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,169 @@
package prc;
import prc.autodoc.ErrorPrinter;
import prc.autodoc.Spinner;
/**
* A menu class. Calls operations from classes in subpackages based on parameters
*
* @author heikki
*/
public class Main {
/** Version number for public releases. Raise by one whenever doing a release. */
private static final int releaseNum = 1;
/** A convenience object for printing both to log and System.err */
public static ErrorPrinter err_pr = new ErrorPrinter();
/** A boolean determining whether to spam the user with progress information */
public static boolean verbose = true;
/** A decorative spinner to look at while the program is loading big files */
public static Spinner spinner = new Spinner();
/**
* Ooh, a main method!
*
* @param args arguments, surprisingly enough
*
* @throws Throwable everything received from the classes called is passed on
*/
public static void main(String[] args) throws Throwable{
if(args.length == 0 || args[0].equals("--help"))
readMe();
String toCall = args[0];
String[] paramsToPass = new String[args.length - 1];
System.arraycopy(args, 1, paramsToPass, 0, paramsToPass.length);
if(toCall.equals("manual")){
prc.autodoc.Main.main(paramsToPass);
}
else if(toCall.equals("2da")){
prc.autodoc.Data_2da.main(paramsToPass);
}
else if(toCall.equals("codegen")){
prc.utils.CodeGen.main(paramsToPass);
}
else if(toCall.equals("radials")){
prc.utils.Radials.main(paramsToPass);
}
else if(toCall.equals("lssubrad")){
prc.utils.ListSubradials.main(paramsToPass);
}
else if(toCall.equals("dupsubrad")){
prc.utils.DuplicateSubradials.main(paramsToPass);
}
else if(toCall.equals("makedep")){
prc.makedep.Main.main(paramsToPass);
}
else if(toCall.equals("upclsfeat")){
prc.utils.AllClassFeatUpdater.main(paramsToPass);
}
else if(toCall.equals("lsentries")){
prc.utils.List2daEntries.main(paramsToPass);
}
else if(toCall.equals("dupentries")){
prc.utils.Duplicate2daEntryDetector.main(paramsToPass);
}
else if(toCall.equals("2datosql")){
prc.utils.SQLMaker.main(paramsToPass);
}
else if(toCall.equals("spellbookmaker")){
prc.utils.SpellbookMaker.main(paramsToPass);
}
else if(toCall.equals("amsspellbookmaker")){
prc.utils.AMSSpellbookMaker.main(paramsToPass);
}
else if(toCall.equals("itempropmaker")){
prc.utils.ItempropMaker.main(paramsToPass);
}
else if(toCall.equals("letoxml")){
prc.utils.LetoListsGenerator.main(paramsToPass);
}
else if(toCall.equals("prec2dagen")){
prc.utils.Precache2daGen.main(paramsToPass);
}
else if(toCall.equals("scrmrchgen")){
prc.utils.ScrollMerchantGen.main(paramsToPass);
}
else if(toCall.equals("npcevol")){
prc.utils.NPCEvolve.main(paramsToPass);
}
else if(toCall.equals("2damerge")){
prc.utils.Data2daMerge.main(paramsToPass);
}
else if(toCall.equals("blank2da")){
prc.utils.Blank2daRows.main(paramsToPass);
}
else if(toCall.equals("validator")){
prc.utils.Validator.main(paramsToPass);
}
else if(toCall.equals("updatedescrft")){
prc.utils.UpdateDes.main(paramsToPass);
}
else if(toCall.equals("scrollgen")){
prc.utils.ScrollGen.main(paramsToPass);
}
else if(toCall.equals("buildscrhack")){
prc.utils.BuildScrollHack.main(paramsToPass);
}
else{
System.out.println("Unknown class: " + toCall);
readMe();
}
}
/**
* Prints the use instructions for this program and kills execution.
*/
private static void readMe(){
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
System.out.println("Usage:\n"+
" java -jar prc.jar [--help] | tool [parameters]\n"+
"\n"+
"tool name of the tool to call. possible values:\n"+
" 2da - Either verifies a single 2da file or compares two\n"+
" 2damerge - Merges 2 directories of 2da files, and outputs to a third\n" +
" blank2da - Blanks rows in a 2da\n" +
" codegen - Autogenerates scripts (or other files)\n"+
" dupentries - Seeks for duplicate entries in the given columns\n" +
" of a given 2da\n"+
" dupsubrad - Seeks through spells.2da and prints lines\n"+
" containing duplicate subradial values\n"+
" letoxml - Creates Leto XML lists from 2da and TLK\n" +
" lsentries - Lists the unique entries in given columns of a\n" +
" given 2da\n" +
" lssubrad - Lists subradial IDs used in spells.2da\n" +
" makedep - Builds include dependency lists\n" +
" radials - Generates subradial FeatID values\n"+
"\n" +
" The following tools are also available, but almost certainly too\n" +
" specialized for general use:\n" +
" 2datosql - Creates a SQL file from 2das\n" +
" itempropmaker - Creates the itemproperty cache item templates\n" +
" manual - Generates the manual\n"+
" npcevol - Alters packages based on logfile scores\n" +
" upclsfeat - Updates base cls_feat_*.2da based on given templates\n" +
" prec2dagen - Creates a 2da file that lists spells/feat.2da rows\n" +
" that should be precached\n" +
" scrmrchgen - Creates scroll merchant based on des_crft_scroll.2da\n" +
" spellbookmaker - Creates and/or updates the new spellbooks data\n" +
" amsspellbookmaker - Creates and/or updates the new AMS spellbooks data\n" +
" validator - Performs a bunch of 2da integrity tests\n" +
" updatedescrft - Updates des_crft_*.2da based on spells.2da\n" +
" scrollgen - Create spell scrolls based on (iprp_)spells.2da\n" +
"\n"+
"parameters a list of parameters passed to the tool called\n"+
"\n"+
"--help prints this info you are reading\n" +
"\n" +
"\n" +
"Release number: " + releaseNum + "\n"
);
System.exit(0);
}
}

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); }
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,222 @@
/**
* A system for calculating nwscript dependencies. Makes some assumptions specific
* to the PRC setup.
*/
package prc.makedep;
import java.util.*;
import java.io.*;
import static prc.Main.*;
/**
* Calculates nwscript dependencies.
*
* Usage: makedep [-aio?] targetlist
* Options:
* -a Append to outputfile. This option must be defined before -o
* -n Ignore include file if not found
* -sPATH[,PATH...] List of source directories
* -oFILE Use FILE as outputfile, stdout assumed
* -?, --help This text
*
*
* @author Ornedan
*/
public class Main {
static Map<String, NSSNode> scripts = new LinkedHashMap<String, NSSNode>();
protected static boolean append = false,
ignoreMissing = false,
error = false;
static PrintStream oStrm = System.out;
/**
* Main method. Entrypoint to the program
*
* @param args The arguments. See readMe() for accepted ones
*/
public static void main(String[] args) {
HashMap<String, String> targetList = new LinkedHashMap<String, String>();
List<String> targetListFileNames = new ArrayList<String>();
boolean targetsFromStdin = false;
// Parse arguments
for(int i = 0; i < args.length; i++) {//[-aio?] targetslist+
// Parameter parseage
String param = args[i];
if(param.startsWith("-")) {
if(param.startsWith("-s")) {
getFiles(param.substring(2));
}
else if(param.startsWith("-o")) {
setOutput(param.substring(2));
}
else {
if(param.equals("--help")) readMe();
else if(param.equals("-")) {
targetsFromStdin = true;
}
else {
for(char c : param.substring(1).toCharArray()){
switch(c) {
case 'a':
append = true;
break;
case 'n':
ignoreMissing = true;
break;
default:
System.out.println("Unknown parameter: " + c);
case '?':
readMe();
}
}
}
}
}
else {
// It's a targets list file
targetListFileNames.add(param);
}
}
// Read targets from stdin if so specified
Scanner scan;
String targetName;
if(targetsFromStdin) {
scan = new Scanner(System.in);
while(scan.hasNextLine()) {
targetName = scan.nextLine().toLowerCase();
// Strip everything after the .ncs from the line
targetName = targetName.substring(0, targetName.indexOf(".ncs") + 4);
targetList.put(NSSNode.getScriptName(targetName), targetName);
}
}
// Parse the target file list
File targetListFile;
for(String fileName : targetListFileNames) {
targetListFile = new File(fileName);
// Read the contents
try {
scan = new Scanner(targetListFile);
} catch (FileNotFoundException e) {
err_pr.println("Error: Could not find file: " + fileName);
error = true;
continue;
}
while(scan.hasNextLine()) {
targetName = scan.nextLine().toLowerCase();
// Strip everything after the .ncs from the line
targetName = targetName.substring(0, targetName.indexOf(".ncs") + 4);
targetList.put(NSSNode.getScriptName(targetName), targetName);
}
}
// Input sanity checks
if(targetList.size() == 0) {
err_pr.println("Error: No targets specified.");
error = true;
}
for(String target : targetList.keySet()) {
if(scripts.get(target.toLowerCase()) == null) {
err_pr.println("Error: Script file for target " + target + " not found in given source directories.");
error = true;
}
}
// Terminate if errored
if(error) System.exit(1);
// TODO: Load the files concurrently. I suspect it will give a slight performance boost
//NSSNode[] debugArr = debugMap.values().toArray(new NSSNode[0]);
for(NSSNode script: scripts.values())
script.linkDirect();
// Check for errors again
if(error) System.exit(1);
// Start a depth-first-search to find all the include trees
for(NSSNode node : scripts.values()){
node.linkFullyAndGetIncludes(null);
//node.printSelf(oStrm, append);
}
// Do the printing
for(String target : targetList.keySet()) {
scripts.get(target.toLowerCase()).printSelf(oStrm, append, targetList.get(target));
}
}
/**
* Prints usage and terminates.
*/
private static void readMe() {
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
System.out.println("Usage: makedep [-aio?] targetslist+\n" +
"Options:\n" +
"-a Append to outputfile. This option must be defined before -o\n"+
"-n Ignore include file if not found\n"+
"-sPATH[,PATH...] List of source directories\n"+
"-oFILE Use FILE as outputfile, stdout assumed\n" +
"- Read targets from stdin. Same format as targets list files\n"+
"-?, --help This text\n"+
"\n"+
"targetslist Name of a file containing a list of object (.ncs) files to\n"+
" generate a make targets for.\n" +
" File format is one path-to-target per line."
);
System.exit(0);
}
/**
* Looks in the directories specified by the given list for .nss
* files and adds them to the sources list.
*
* @param pathList comma-separated list of directories to look in
*/
private static void getFiles(String pathList){
String[] paths = pathList.split(",");
File[] files;
String temp;
for(String path : paths){
files = new File(path).listFiles(new FileFilter(){
public boolean accept(File file){
return file.getName().endsWith(".nss");
}
});
for(File file: files){
temp = NSSNode.getScriptName(file.getName()).toLowerCase();
if(!scripts.containsKey(temp))
scripts.put(temp, new NSSNode(file.getPath()));
else{
err_pr.println("Error: Duplicate script file: " + temp);
error = true;
}
}
}
}
/**
* Sets the output to write the results to to a file specified by the
* given filename.
* If the file is not found, the program terminates.
*
* @param outFileName Name of the file to print results to
*/
private static void setOutput(String outFileName){
try{
oStrm = new PrintStream(new FileOutputStream(outFileName, append), true);
}catch(FileNotFoundException e){
err_pr.println("Error: Missing output file " + outFileName + "\nTerminating!");
System.exit(1);
}
}
}

View File

@@ -0,0 +1,89 @@
package prc.makedep;
import java.io.*;
import java.util.*;
public class MakedepChecker {
private static class Node{
private HashSet<String> deps = new HashSet<String>();
private String full;
public Node(String full, String sdeps) {
this.full = full;
String[] sdepsar = sdeps.split(" ");
for(String s : sdepsar)
if(!s.equals(""))
deps.add(s);
}
public void compare(Node another){
if(!(this.deps.containsAll(another.deps) && another.deps.containsAll(this.deps))){
System.out.println("Differing lines:\n " + this.full + "\n " + another.full);
if(!this.deps.containsAll(another.deps)){
System.out.println(" Missing in olds:");
HashSet<String> temp = another.deps;
temp.removeAll(this.deps);
for(String s : temp)
System.out.println(" " + s);
}
if(!another.deps.containsAll(this.deps)){
System.out.println(" Missing in news:");
HashSet<String> temp = this.deps;
temp.removeAll(another.deps);
for(String s : temp)
System.out.println(" " + s);
}
}
}
}
public static void main(String[] args) throws Throwable{
// Load old and new lists
File odf = new File("oldmakedep.txt");
File ndf = new File("newmakedep.txt");
HashMap<String, Node> olds = new HashMap<String, Node>();
HashMap<String, Node> news = new HashMap<String, Node>();
Scanner scan = new Scanner(odf);
while(scan.hasNextLine()){
String s = scan.nextLine();
if(s.equals(""))
continue;
String[] ss = s.split(":");
olds.put(ss[0], new Node(s, ss[1]));
}
scan = new Scanner(ndf);
while(scan.hasNextLine()){
String s = scan.nextLine();
if(s.equals(""))
continue;
String[] ss = s.split(":");
news.put(ss[0], new Node(s, ss[1]));
}
if(!(olds.keySet().containsAll(news.keySet()) &&
news.keySet().containsAll(olds.keySet())
)){
System.out.println("Differing line counts!");
Set<String> foo = olds.keySet();
foo.removeAll(news.keySet());
System.out.println("Files not defined in news:");
for(String s : foo)
System.out.println(s);
foo = news.keySet();
foo.removeAll(olds.keySet());
System.out.println("Files not defined in olds:");
for(String s : foo)
System.out.println(s);
}
for(String key : olds.keySet()){
olds.get(key).compare(news.get(key));
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,259 @@
package prc.makedep;
import java.io.*;
import java.util.*;
import static prc.Main.*;
/**
* A node in an NWScript include tree.
*
* @author Ornedan
*/
public class NSSNode {
private static enum STATES{
/**
* This node has not been visited yet in the full linking phase.
*/
UNSTARTED,
/**
* The depth-first-search has reached this node, but has not returned to it yet.
*/
WORKING,
/**
* All the includes for this node have been resolved.
*/
DONE};
private STATES state = STATES.UNSTARTED;
/**
* While the node is in WORKING state, it's includes have not been resolved yet.
* So, if a circular include is found, it is added here so that this node's includes
* can be added to it's list once the DFS returns to this node.
*/
private HashSet<NSSNode> mergeLater = new HashSet<NSSNode>();
/**
* A container for marking files that are already being handled during a merging cascade.
*/
private static HashSet<NSSNode> inMerge;
/**
* The nodes included directly by this node via #include statements.
*/
private HashSet<NSSNode> adjenct = new LinkedHashSet<NSSNode>();
/**
* The nodes included by this node either directly or via intermediary steps. Also,
* each file is considered to include itself in order to make printing include lists
* a bit less complex.
*/
private HashSet<NSSNode> includes = new LinkedHashSet<NSSNode>();
/**
* The first node to call linkFullyAndGetIncludes() on this node.
*/
public NSSNode priCaller;
/**
* The path used to reference this file.
*/
public String fileName;
/**
* Creates a new, unlinked NSSNode
*
* @param fileName
*/
public NSSNode(String fileName) {
fileName = fileName.toLowerCase();
// Make sure the file exists
if(!new File(fileName).exists()){
Main.error = true;
err_pr.println("Error: Missing script file: " + fileName);
return;
}
// Each node is dependent on itself
includes.add(this);
this.fileName = fileName;
}
/**
* Link this script file to those files it directly includes.
*/
public void linkDirect(){
// Load the text for this file
File file = new File(fileName);
char[] cArray = new char[(int)file.length()];
try{
FileReader fr = new FileReader(fileName);
fr.read(cArray);
fr.close();
}catch(Exception e){
err_pr.println("Error: Error while reading file: " + fileName);
Main.error = true;
return;
}
/* Debuggage
if(fileName.indexOf("psi_inc_psifunc") != -1)
System.currentTimeMillis();
//*/
String[] directIncludes = NWScript.findIncludes(new String(cArray));
for(String name : directIncludes){
name = name.toLowerCase();
NSSNode adj = Main.scripts.get(name);
if(adj != null){
if(adjenct.contains(adj))
//System.out.println("Warning: " + getScriptName(fileName) + " includes " + getScriptName(adj.fileName) + " multiple times");
System.out.printf("Warning: %-16s includes %-16s multiple times\n",
getScriptName(fileName),
getScriptName(adj.fileName)
);
adjenct.add(adj);
}else if(!Main.ignoreMissing){
System.out.println("Script file not found: " + name);
Main.error = true;
}
}
}
/**
* Builds the full list of files included by this script.
*
* @param caller the node calling this function recursively passes a reference
* to itself. Used in cases where the target node was already in
* WORKING state.
* @return HashSet containing the fully resolved list of files included by this one
*/
public HashSet<NSSNode> linkFullyAndGetIncludes(NSSNode caller) {
if(state != STATES.UNSTARTED)
if(state == STATES.DONE)
return includes;
else{
// Add to a list to merge include lists later once this node is done
NSSNode toMergeLater = caller;
while(toMergeLater != this){
mergeLater.add(toMergeLater);
toMergeLater = toMergeLater.priCaller;
}
return null;
}
/* Debuggage stuff
if(//fileName.indexOf("ss_ep_psionicsal") != -1 ||
//fileName.indexOf("psi_inc_psifunc") != -1 ||
//fileName.indexOf("prc_alterations") != -1 ||
//fileName.indexOf("bonsum_shapelem") != -1 ||
fileName.indexOf("pnp_shft_poly") != -1 ||
false
)
System.currentTimeMillis();
//*/
// Mark the node as work-in-progress, so it won't be handled again
state = STATES.WORKING;
priCaller = caller;
// Initialize the includes list for this script with the direct includes
includes.addAll(adjenct);
HashSet<NSSNode> temp;
for(NSSNode adj : adjenct){
temp = adj.linkFullyAndGetIncludes(this);
if(temp != null)
includes.addAll(temp);
}
// Do the delayed include list merges
inMerge = new HashSet<NSSNode>();
this.doMerge();
/*for(NSSNode node : mergeLater)
node.includes.addAll(this.includes);*/
inMerge = null;
state = STATES.DONE;
return includes;
}
/**
* Merges the include list of this file with all files that included this one while
* it was still being worked on. Recurses to each of these files in order to update
* files dependent on them.
*/
private void doMerge(){
for(NSSNode node : mergeLater){
if(!inMerge.contains(node)){
inMerge.add(node);
node.includes.addAll(this.includes);
node.doMerge();
}
}
}
/**
* Gets the name used in include statements for the given filename.
*
* @param path the path to parse
* @return string, name used in include statements
*/
public static String getScriptName(String path) {
// Cut out the .nss or .ncs
try{
path = path.substring(0, path.indexOf(".nss") != -1 ? path.indexOf(".nss") : path.indexOf(".ncs"));
}catch(Exception e){
err_pr.println(path);
}
// Cut out the directories, if present
if(path.indexOf(File.separator) != -1)
path = path.substring(path.lastIndexOf(File.separator) + 1, path.length());
return path;
}
/**
* Prints this node's list of includes to a stream.
*
* @param strm stream to print to
* @param append if this is <code>true</code>, the PrintStream's <code>append</code> methods
* are used instead of <code>print</code> methods.
* @param target name of the object file to be built from this script
*/
public void printSelf(PrintStream strm, boolean append, String target) {
String lineSeparator = System.getProperty("line.separator");
if(append){
//strm.append(fileName.replace(".nss", ".ncs") + ":"/* + fileName*/);
strm.append(target + ":");
for(NSSNode include : includes)
strm.append(" " + include.fileName);
strm.append(lineSeparator + lineSeparator);
}else{
strm.print(target + ":");
for(NSSNode include : includes)
strm.print(" " + include.fileName);
strm.print(lineSeparator + lineSeparator);
}
}
/**
* A test main.
*
* @param args Ye olde parameters. Doesn't accept any
*/
public static void main(String[] args){
System.out.println(getScriptName("C:\\foo\\bar\\meh.nss"));
System.out.println(getScriptName("boo.nss"));
System.out.println(getScriptName("lar\\floob.nss"));
}
/**
* An NSSNode's string representation is it's filename.
*
* @see java.lang.Object#toString()
*/
public String toString(){return fileName;}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,144 @@
package prc.makedep;
import java.util.*;
import java.util.regex.*;
/**
* A class for parsing NWScript files.
*
* @author Ornedan
*/
public class NWScript {
private static enum STATES{
/**
* Normal code, can switch to the other modes from here.
*/
NORMAL,
/**
* String mode. Exited by an unescaped " (technically should be ended
* by a new line as well, but we assume the code is not that badly fubar'd).
*/
STRING,
/**
* Singe line comment, started by //. Exited by a newline.
*/
SCOMMENT,
/**
* Multi-line comment.
*/
MCOMMENT
};
private static Matcher matcher = Pattern.compile("#include[ \t\u000B\f\r]*\"(.*)\"").matcher("");
/**
* Parses the given source text for #include directives and returns the names of
* the files included.
*
* @param srcText contents of a nwscript source file
* @return array of String containing the names of files included by this one
*/
public static String[] findIncludes(CharSequence srcText){
StringBuffer wip = new StringBuffer(srcText);
ArrayList<String> list = new ArrayList<String>();
removeComments(wip);
// Parse for remaining #include statements
//#include[ \t\x0B\f\r]*"(.*)"
matcher.reset(wip);
//String debug;
while(matcher.find()){
//debug = matcher.group(1);
//list.add(debug);
list.add(matcher.group(1));
}
return list.toArray(new String[0]);
}
/**
* Replaces the contents of comments with spaces.
*
* @param sbuf Stringbuffer containing nwscript to strip comments from
*/
private static void removeComments(StringBuffer sbuf){
STATES state = STATES.NORMAL;
char prev = '\u0000', cur;
boolean evenBSlash = true;
for(int i = 0; i < sbuf.length(); i++){
cur = sbuf.charAt(i);
switch(state){
case NORMAL:
if(prev == '/'){
if(cur == '/'){
state = STATES.SCOMMENT;
sbuf.setCharAt(i - 1, ' ');
sbuf.setCharAt(i, ' ');
}
else if(cur == '*'){
state = STATES.MCOMMENT;
sbuf.setCharAt(i - 1, ' ');
sbuf.setCharAt(i, ' ');
// Null current so that /*/ won't get mistakenly detected as having closed
cur = ' ';
}
}
else if(cur == '\"')
state = STATES.STRING;
break;
case STRING:
if(cur == '\"'){
if(evenBSlash)
state = STATES.NORMAL;
}
else if(cur == '\\')
evenBSlash = !evenBSlash;
else
evenBSlash = true;
break;
case SCOMMENT:
if(cur == '\n')
state = STATES.NORMAL;
else
sbuf.setCharAt(i, ' ');
break;
case MCOMMENT:
if(prev == '*' && cur == '/')
state = STATES.NORMAL;
sbuf.setCharAt(i, ' ');
break;
}
prev = cur;
}
}
/**
* Test main
* @param args
*/
public static void main(String[] args) {
findIncludes(
"/**\n" +
" * Mof! \" \"\n" +
" */\n" +
"#include \"test\"\n" +
"#include \"tset\"\n" +
"/*/ */\n"+
"\n"+
"void main()\n"+
"{// Rar!\n"+
" florb(); /* call florb */\n"+
" /* another call */ florb();\n"+
"}\n"
);
}
}

View File

@@ -0,0 +1,3 @@
Main-Class: prc.Main

View File

@@ -0,0 +1,691 @@
package prc.utils;
import prc.autodoc.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static prc.Main.spinner;
/**
*
*/
public final class AMSSpellbookMaker {
private AMSSpellbookMaker() {/* Prevent instantiation */}
private static int spells2daRow = 0;
private static int feat2daRow = 0;
private static int iprp_feats2daRow = 0;
private static int tlkRow = 0;
private static int classSpellRow = 0;
private static int classFeatRow = 0;
private static int subradialID = 7000;
private static Data_2da classes2da;
private static Data_2da spells2da;
private static Data_2da feat2da;
private static Data_2da iprp_feats2da;
private static Data_TLK customtlk;
private static Data_TLK dialogtlk;
private static Data_2da classSpell2da;
private static Data_2da classFeat2da;
private static String[] spellLabels;
private static final int MAGIC_TLK = 0x01000000;
private static String start_label = "####START_OF_AMS_SPELLBOOK_RESERVE";
private static String end_label = "####END_OF_AMS_SPELLBOOK_RESERVE";
private static String spellbook_filename_start = "cls_spcr_";
private static String class_filename_start = "cls_spell_";
private static String AMSheader = "prc_";
private static int classlength = 5;
/**
* The main method, as usual.
*
* @param args do I really need to explain this?
* @throws Exception this is a simple tool, just let all failures blow up
*/
public static void main(String[] args) throws Exception {
// Parse args
for (String param : args) {//[--help]
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else if (param.equals("-tob")) {
classlength = 3;
spellbook_filename_start = "cls_mvcr_";
AMSheader = "tob_";
class_filename_start = "cls_move_";
start_label = "####START_OF_TOB_SPELLBOOK_RESERVE";
end_label = "####END_OF_TOB_SPELLBOOK_RESERVE";
System.out.println("Assembling Tome of Battle spellbooks...");
} else if (param.equals("-inv")) {
classlength = 3;
spellbook_filename_start = "cls_ivcr_";
AMSheader = "inv_";
class_filename_start = "cls_inv_";
start_label = "####START_OF_INV_SPELLBOOK_RESERVE";
end_label = "####END_OF_INV_SPELLBOOK_RESERVE";
System.out.println("Assembling Invocation spellbooks...");
} else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
System.out.println("Unknown parameter: " + param);
readMe();
}
}
//load all the data files in advance
//this is quite slow, but needed
classes2da = Data_2da.load2da("2das" + File.separator + "classes.2da", true);
spells2da = Data_2da.load2da("2das" + File.separator + "spells.2da", true);
feat2da = Data_2da.load2da("2das" + File.separator + "feat.2da", true);
iprp_feats2da = Data_2da.load2da("2das" + File.separator + "iprp_feats.2da", true);
customtlk = new Data_TLK("tlk" + File.separator + "prc_consortium.tlk");
dialogtlk = new Data_TLK("tlk" + File.separator + "dialog.tlk");
spellLabels = spells2da.getLabels();
//get the start/end rows for each file for the reserved blocks
getFirstSpells2daRow();
getFirstFeat2daRow();
getFirstIPRPFeats2daRow();
getFirstTlkRow();
System.out.println("First free spells.2da row is " + spells2daRow);
System.out.println("First free feat.2da row is " + feat2daRow);
System.out.println("First free iprp_feats.2da row is " + iprp_feats2daRow);
System.out.println("First free tlk row is " + tlkRow);
//now process each class in turn
for (int classRow = 0; classRow < classes2da.getEntryCount(); classRow++) {
//the feat file is the root of the file naming layout
String classfilename = classes2da.getEntry("FeatsTable", classRow);
//check its a real class not padding
if (classfilename != null && classfilename.length() > 9) {
classfilename = classfilename.substring(9);
String classCoreFilename = spellbook_filename_start + classfilename;
//check the file exists
File classCoreFile = new File("2das" + File.separator + classCoreFilename + ".2da");
if (classCoreFile.exists()) {
//open the core spell file
Data_2da classCoreSpell2da = Data_2da.load2da(classCoreFile.getPath(), true);
// If the cls_spell file for this class does not exist yet, create it
File classSpell2daFile = new File("2das" + File.separator + class_filename_start + classfilename + ".2da");
if (!classSpell2daFile.exists()) {
System.out.println("File " + classSpell2daFile.getPath() + " did not exist, creating");
classSpell2da = new Data_2da(class_filename_start + classfilename, "");
classSpell2da.addColumn("Label");
classSpell2da.addColumn("Level");
classSpell2da.addColumn("FeatID");
classSpell2da.addColumn("IPFeatID");
classSpell2da.addColumn("SpellID");
classSpell2da.addColumn("RealSpellID");
classSpell2da.addColumn("ReqFeat");
} else {
classSpell2da = Data_2da.load2da(classSpell2daFile.getPath(), true);
// Clear all the existing rows
for (String label : classSpell2da.getLabels())
for (int i = 0; i < classSpell2da.getEntryCount(); i++)
classSpell2da.setEntry(label, i, "****");
}
// Make sure the file contains at least one line
if (classSpell2da.getEntryCount() == 0) {
classSpell2da.appendRow();
}
// The first line should be left blank, so initialise index to 1
classSpellRow = 1;
// Load the class feats 2da
classFeat2da = Data_2da.load2da("2das" + File.separator + "cls_feat_" + classfilename + ".2da", true);
getFirstClassFeat2daRow();
//get the class name
String className = getCheckedTlkEntry(classes2da.getBiowareEntryAsInt("Name", classRow)) + " ";
// Construct the class portion of labels to be generated
String classLabel = classes2da.getEntry("Label", classRow);
// Nothing longer than Suel_Archanamach (16 chars) allowed in order to avoid extending the length of all spells.2da rows again
if (classLabel.length() > 16)
classLabel = classLabel.substring(0, 17);
classLabel += "_";
//get the maximum spell level
int maxLevel = 0;
for (int row = 0; row < classCoreSpell2da.getEntryCount(); row++)
maxLevel = Math.max(maxLevel, classCoreSpell2da.getBiowareEntryAsInt("Level", row));
//loop over all the spells
for (int row = 0; row < classCoreSpell2da.getEntryCount(); row++) {
//check its not a null row
if (!classCoreSpell2da.getEntry("SpellID", row).equals("****")) {
//get the real spellID
int spellID = classCoreSpell2da.getBiowareEntryAsInt("SpellID", row);
//get the level of the spell
int spellLevel = classCoreSpell2da.getBiowareEntryAsInt("Level", row);
//get the metamagic reference to know what types work
int metamagic = spells2da.getBiowareEntryAsInt("Metamagic", spellID);
/* not really necessary
if(metamagic == 0)
System.out.println("Check metamagic for spell " + spellID);
*/
// Hack - Determine how radial masters there might be: 1 + metamagics
int masterCount = 1;
for (int metamagicFlag = 0x1; metamagicFlag <= 0x20; metamagicFlag <<= 1)
if ((metamagic & metamagicFlag) != 0) {
/*
* 0x01 = 1 = Empower
* 0x02 = 2 = Extend
* 0x04 = 4 = Maximize
* 0x08 = 8 = Quicken
* 0x10 = 16 = Silent
* 0x20 = 32 = Still
*/
int metaCost = 0;
if (metamagicFlag == 0x01) metaCost = 2;
else if (metamagicFlag == 0x02) metaCost = 1;
else if (metamagicFlag == 0x04) metaCost = 3;
else if (metamagicFlag == 0x08) metaCost = 4;
else if (metamagicFlag == 0x10) metaCost = 1;
else if (metamagicFlag == 0x20) metaCost = 1;
if (spellLevel + metaCost <= maxLevel)
masterCount += 1;
}
List<Integer> preReservedClassSpell2daRows = new ArrayList<Integer>();
// Reserve a number of cls_spell_ rows for the main entries.
for (int i = 0; i < masterCount; i++) {
// If needed, add rows to the file to prevent errors in addNewSpellbookData
if (classSpellRow >= classSpell2da.getEntryCount()) {
classSpell2da.appendRow();
}
preReservedClassSpell2daRows.add(classSpellRow++);
}
// Generate an iterator for it
Iterator<Integer> preReservedClassSpell2daRowIterator = preReservedClassSpell2daRows.iterator();
//now loop over the metamagic varients
//-1 represents no metamagic
for (int metamagicNo = -1; metamagicNo < 6; metamagicNo++) {
/*
* 0x01 = 1 = Empower
* 0x02 = 2 = Extend
* 0x04 = 4 = Maximize
* 0x08 = 8 = Quicken
* 0x10 = 16 = Silent
* 0x20 = 32 = Still
*/
// If creating the base entry, or the given metamagic applies
if (metamagicNo == -1 || (metamagic & (1 << metamagicNo)) != 0) {
String spellNameMetamagic = "";
String spellLabelMetamagic = "";
String metaScript = "";
int metamagicLevel = 0;
String metamagicFeat = "****";
if (metamagicNo == -1) {
spellNameMetamagic = "";
spellLabelMetamagic = "";
metaScript = "sp";
metamagicLevel = 0;
metamagicFeat = "****";
} else if (metamagicNo == 0) {
spellNameMetamagic = "Empowered ";
spellLabelMetamagic = "Empowered_";
metaScript = "em";
metamagicLevel = 2;
metamagicFeat = "11";
} else if (metamagicNo == 1) {
spellNameMetamagic = "Extended ";
spellLabelMetamagic = "Exteneded_";
metaScript = "ex";
metamagicLevel = 1;
metamagicFeat = "12";
} else if (metamagicNo == 2) {
spellNameMetamagic = "Maximized ";
spellLabelMetamagic = "Maximized_";
metaScript = "ma";
metamagicLevel = 3;
metamagicFeat = "25";
} else if (metamagicNo == 3) {
spellNameMetamagic = "Quickened ";
spellLabelMetamagic = "Quickened_";
metaScript = "qu";
metamagicLevel = 4;
metamagicFeat = "29";
} else if (metamagicNo == 4) {
spellNameMetamagic = "Silent ";
spellLabelMetamagic = "Silent_";
metaScript = "si";
metamagicLevel = 1;
metamagicFeat = "33";
} else if (metamagicNo == 5) {
spellNameMetamagic = "Still ";
spellLabelMetamagic = "Still_";
metaScript = "st";
metamagicLevel = 1;
metamagicFeat = "37";
}
//check if the metamagic adjusted level is less than the maximum level
if ((metamagicLevel + spellLevel) <= maxLevel) {
//debug printout
//System.out.println(name+" : "+label);
addNewSpellbookData(spellID,
classfilename,
metaScript,
metamagicNo,
metamagicLevel,
metamagicFeat,
spellLevel,
className,
spellNameMetamagic,
classLabel,
spellLabelMetamagic,
preReservedClassSpell2daRowIterator,
0);
}//end of level check
}//end of metamamgic check
}//end of metamagic loop
}// end if - The cls_spcr row contains an entry
}//end of cls_spells_*_core.2da loop
//save the new cls_spell_*.2da file
classSpell2da.save2da("2das", true, true);
classFeat2da.save2da("2das", true, true);
} else {
//System.out.println(classfilename+" does not exist.");
}
}
}
//now resave the files we opened
spells2da.save2da("2das", true, true);
feat2da.save2da("2das", true, true);
iprp_feats2da.save2da("2das", true, true);
customtlk.saveAsXML("prc_consortium", "tlk", true);
}
private static void addNewSpellbookData(int spellID,
String classfilename,
String metaScript,
int metamagicNo,
int metamagicLevel,
String metamagicFeat,
int spellLevel,
String className,
String spellNameMetamagic,
String classLabel,
String spellLabelMetamagic,
Iterator<Integer> preReservedClassSpell2daRows,
int subradialMaster) {
// Hack - If not a subradial, use prereserved cls_spell row
int localClassSpellRow;
if (subradialMaster == 0) {
localClassSpellRow = preReservedClassSpell2daRows.next();
} else {
// Grab the current value of classSpellRow for use and then increment it
localClassSpellRow = classSpellRow++;
}
//get the name of the spell
String spellName = getCheckedTlkEntry(spells2da.getBiowareEntryAsInt("Name", spellID));
//get the label of the spell
String spellLabel = spells2da.getEntry("Label", spellID);
//assemble the name
String name = className + spellNameMetamagic + spellName;
//assemble the label
String label = classLabel + spellLabelMetamagic + spellLabel;
//set the next tlk line to the name
customtlk.setEntry(tlkRow, name);
//copy the original spells.2da line to the next free spells.2da line
String[] originalSpellRow = spells2da.getRow(spellID);
for (int i = 0; i < originalSpellRow.length; i++) {
spells2da.setEntry(spellLabels[i], spells2daRow, spells2da.getEntry(spellLabels[i], spellID));
}
//change the ImpactScript
String script = AMSheader + (classfilename.length() <= 5 ? classfilename : classfilename.substring(0, classlength)) + "_generic";
spells2da.setEntry("ImpactScript", spells2daRow, script);
//change the Label
spells2da.setEntry("Label", spells2daRow, label);
//change the Name
spells2da.setEntry("Name", spells2daRow, Integer.toString(tlkRow + MAGIC_TLK));
//if quickened, set conjuring/casting duration to zero
if (metamagicNo == 3) {
spells2da.setEntry("ConjTime", spells2daRow, "0");
spells2da.setEntry("CastTime", spells2daRow, "0");
}
//if silenced, set it to no vocals
if (metamagicNo == 4) {
spells2da.setEntry("ConjSoundVFX", spells2daRow, "****");
spells2da.setEntry("ConjSoundMale", spells2daRow, "****");
spells2da.setEntry("ConjSoundFemale", spells2daRow, "****");
spells2da.setEntry("CastSound", spells2daRow, "****");
}
//if stilled, set it to no casting animations
if (metamagicNo == 5) {
spells2da.setEntry("CastAnim", spells2daRow, "****");
spells2da.setEntry("ConjAnim", spells2daRow, "****");
}
//set the level to the correct value, including metamagic
spells2da.setEntry("Innate", spells2daRow, Integer.toString(metamagicLevel + spellLevel));
//clear class levels
spells2da.setEntry("Bard", spells2daRow, "****");
spells2da.setEntry("Cleric", spells2daRow, "****");
spells2da.setEntry("Druid", spells2daRow, "****");
spells2da.setEntry("Paladin", spells2daRow, "****");
spells2da.setEntry("Ranger", spells2daRow, "****");
spells2da.setEntry("Wiz_Sorc", spells2daRow, "****");
// set subradial master, if applicable
if (subradialMaster != 0) {
spells2da.setEntry("Master", spells2daRow, Integer.toString(subradialMaster));
//calculate the new feat id
int subradialFeatID = spells2da.getBiowareEntryAsInt("FeatID", subradialMaster);
//Set the FEATID on each of the subspells as follows: (65536 * subfeat) + feat ID.
//The top 16 bits is used for subfeat, the bottom for feat.
subradialFeatID = (65536 * subradialID) + subradialFeatID;
spells2da.setEntry("FeatID", spells2daRow, Integer.toString(subradialFeatID));
} else {
spells2da.setEntry("Master", spells2daRow, "****");
//make it point to the new feat.2da line that will be added soon
spells2da.setEntry("FeatID", spells2daRow, Integer.toString(feat2daRow));
}
//remove projectiles from firing because the real spell will do this
spells2da.setEntry("Proj", spells2daRow, "0");
spells2da.setEntry("ProjModel", spells2daRow, "****");
spells2da.setEntry("ProjType", spells2daRow, "****");
spells2da.setEntry("ProjSpwnPoint", spells2daRow, "****");
spells2da.setEntry("ProjSound", spells2daRow, "****");
spells2da.setEntry("ProjOrientation", spells2daRow, "****");
spells2da.setEntry("HasProjectile", spells2daRow, "0");
//add a feat.2da line
if (subradialMaster == 0) {
// Clear the line of old values
for (String featLabel : feat2da.getLabels())
feat2da.setEntry(featLabel, feat2daRow, "****");
// Reset the ReqAction column to 1
feat2da.setEntry("ReqAction", feat2daRow, "1");
//make it point to the new spells.2da line
feat2da.setEntry("SPELLID", feat2daRow, Integer.toString(spells2daRow));
//change the Name
feat2da.setEntry("FEAT", feat2daRow, Integer.toString(tlkRow + MAGIC_TLK));
//change the Label
feat2da.setEntry("LABEL", feat2daRow, label);
//change the description
feat2da.setEntry("DESCRIPTION", feat2daRow, spells2da.getEntry("SpellDesc", spells2daRow));
//change the icon
feat2da.setEntry("ICON", feat2daRow, spells2da.getEntry("IconResRef", spells2daRow));
//personal range
feat2da.setEntry("TARGETSELF", feat2daRow, spells2da.getEntry("Range", spellID).equals("P") ? "1" : "0");
//if spell is hostile, make feat hostile
if (spells2da.getEntry("HostileSetting", spellID).equals("1")) {
feat2da.setEntry("HostileFeat", feat2daRow, "1");
} else {
feat2da.setEntry("HostileFeat", feat2daRow, "0");
}
//set the category to the same as the spell
feat2da.setEntry("CATEGORY", feat2daRow, spells2da.getEntry("Category", spells2daRow));
}
//add an iprp_feats.2da line
if (subradialMaster == 0) {
// Clear the line of old values
for (String iprpLabel : iprp_feats2da.getLabels())
iprp_feats2da.setEntry(iprpLabel, iprp_feats2daRow, "****");
//set its label
iprp_feats2da.setEntry("Label", iprp_feats2daRow, label);
//set its name
iprp_feats2da.setEntry("Name", iprp_feats2daRow, Integer.toString(tlkRow + MAGIC_TLK));
//make it point to the new feat.2da line
iprp_feats2da.setEntry("FeatIndex", iprp_feats2daRow, Integer.toString(feat2daRow));
//set its cost to 0.0
iprp_feats2da.setEntry("Cost", iprp_feats2daRow, "0.0");
}
//add a cls_spell_*.2da line if needed
if (localClassSpellRow >= classSpell2da.getEntryCount()) {
classSpell2da.appendRow();
}
//set its label
classSpell2da.setEntry("Label", localClassSpellRow, label);
//make it point to the new spells.2da
classSpell2da.setEntry("SpellID", localClassSpellRow, Integer.toString(spells2daRow));
//make it point to the old spells.2da
classSpell2da.setEntry("RealSpellID", localClassSpellRow, Integer.toString(spellID));
//if its a subradial, dont do this
if (subradialMaster == 0) {
//make it point to the new feat.2da
classSpell2da.setEntry("FeatID", localClassSpellRow, Integer.toString(feat2daRow));
//make it point to the new iprp_feats.2da
classSpell2da.setEntry("IPFeatID", localClassSpellRow, Integer.toString(iprp_feats2daRow));
//add the metamagic checks
classSpell2da.setEntry("ReqFeat", localClassSpellRow, metamagicFeat);
//set its level
classSpell2da.setEntry("Level", localClassSpellRow, Integer.toString(metamagicLevel + spellLevel));
} else {
//make it point to the new feat.2da
classSpell2da.setEntry("FeatID", localClassSpellRow, "****");
//make it point to the new iprp_feats.2da
classSpell2da.setEntry("IPFeatID", localClassSpellRow, "****");
//add the metamagic checks
classSpell2da.setEntry("ReqFeat", localClassSpellRow, "****");
//set its level
classSpell2da.setEntry("Level", localClassSpellRow, "****");
}
//cls_feat_*.2da
if (subradialMaster == 0) {
classFeat2da.setEntry("FeatLabel", classFeatRow, label);
classFeat2da.setEntry("FeatIndex", classFeatRow, Integer.toString(feat2daRow));
classFeat2da.setEntry("List", classFeatRow, Integer.toString(0));
classFeat2da.setEntry("GrantedOnLevel", classFeatRow, Integer.toString(99));
classFeat2da.setEntry("OnMenu", classFeatRow, Integer.toString(1));
}
//move to next file lines
getNextSpells2daRow();
getNextTlkRow();
//only need new ones of these if its not a subradial
if (subradialMaster == 0) {
getNextFeat2daRow();
getNextIPRPFeats2daRow();
getNextClassFeat2daRow();
} else { //do this if it is a subradial
// increase the subradial id ready for next one
subradialID++;
}
//add subradial spells
if (subradialMaster == 0) {
//store the spell row the master uses
//will be incremented by subradials
//the -1 is because you want the last used row, not the current blank row
int masterSpellID = spells2daRow - 1;
boolean doOnce = false;
for (int subradial = 1; subradial <= 5; subradial++) {
if (spells2da.getBiowareEntryAsInt("SubRadSpell" + subradial, spellID) != 0) {
// It is, in fact, a radial master, so set it's ImpactScript to tell people
// about the BioBug triggered by using subradial feats directly from class radial
if (!doOnce) {
doOnce = true;
spells2da.setEntry("ImpactScript", masterSpellID, "prc_radialbug");
}
addNewSpellbookData(spells2da.getBiowareEntryAsInt("SubRadSpell" + subradial, spellID),
classfilename,
metaScript,
metamagicNo,
metamagicLevel,
metamagicFeat,
spellLevel,
className,
spellNameMetamagic,
classLabel,
spellLabelMetamagic,
preReservedClassSpell2daRows,
masterSpellID);
//update the master rows with the subradial spell rows
//the -1 is because you want the last used row, not the current blank row
spells2da.setEntry("SubRadSpell" + subradial, masterSpellID, Integer.toString(spells2daRow - 1));
}
}
}
}
private static void getFirstSpells2daRow() {
System.out.print("Finding start of spells.2da ");
while (!spells2da.getEntry("Label", spells2daRow).equals(start_label)) {
spells2daRow++;
if (spells2daRow > spells2da.getEntryCount()) {
System.out.println("Spells.2da reached the end of the file.");
System.exit(1);
}
spinner.spin();
}
spells2daRow++;
System.out.println("- Done");
}
private static void getNextSpells2daRow() {
spells2daRow++;
if (spells2da.getEntry("Label", spells2daRow).equals(end_label)) {
getFirstSpells2daRow();
}
}
private static void getFirstFeat2daRow() {
System.out.print("Finding start of feat.2da ");
while (!feat2da.getEntry("Label", feat2daRow).equals(start_label)) {
feat2daRow++;
if (feat2daRow > feat2da.getEntryCount()) {
System.out.println("Feat.2da reached the end of the file.");
System.exit(1);
}
spinner.spin();
}
feat2daRow++;
System.out.println("- Done");
}
private static void getNextFeat2daRow() {
feat2daRow++;
if (feat2da.getEntry("Label", feat2daRow).equals(end_label)) {
getFirstFeat2daRow();
}
}
private static void getFirstIPRPFeats2daRow() {
System.out.print("Finding start of iprp_spells.2da ");
while (!iprp_feats2da.getEntry("Label", iprp_feats2daRow).equals(start_label)) {
iprp_feats2daRow++;
if (iprp_feats2daRow > iprp_feats2da.getEntryCount()) {
System.out.println("iprp_feats.2da reached the end of the file.");
System.exit(1);
}
spinner.spin();
}
iprp_feats2daRow++;
System.out.println("- Done");
}
private static void getNextIPRPFeats2daRow() {
iprp_feats2daRow++;
if (iprp_feats2da.getEntry("Label", iprp_feats2daRow).equals(end_label)) {
getFirstIPRPFeats2daRow();
}
}
private static void getFirstClassFeat2daRow() {
classFeatRow = 0;
System.out.print("Finding start of cls_feat_*.2da ");
while (!classFeat2da.getEntry("FeatLabel", classFeatRow).equals(start_label)) {
classFeatRow++;
if (classFeatRow >= classFeat2da.getEntryCount()) {
System.out.println("cls_feat_*.2da reached the end of the file.");
System.exit(1);
}
spinner.spin();
}
getNextClassFeat2daRow();
System.out.println("- Done");
}
private static void getNextClassFeat2daRow() {
classFeatRow++;
if (classFeat2da.getEntry("FeatLabel", classFeatRow).equals(end_label)) {
classFeat2da.insertRow(classFeatRow);
}
}
private static void getFirstTlkRow() {
System.out.print("Finding start of prc_consortium.tlk ");
while (!customtlk.getEntry(tlkRow).equals(start_label)) {
tlkRow++;
spinner.spin();
}
tlkRow++;
System.out.println("- Done");
}
private static void getNextTlkRow() {
tlkRow++;
if (customtlk.getEntry(tlkRow).equals(end_label)) {
getFirstTlkRow();
}
}
private static String getCheckedTlkEntry(int entryNo) {
if (entryNo > MAGIC_TLK) {
return customtlk.getEntry(entryNo - MAGIC_TLK);
}
return dialogtlk.getEntry(entryNo);
}
private static void readMe() {
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
System.out.println("Usage:\n" +
" [--help]\n" +
"\n" +
" --help prints this text\n" +
"\n" +
" -tob Tome of Battle" +
" -inv Invocations" +
"\n" +
"Creates and/or updates the new AMS spellbooks data. Assumes it's being run from\n" +
"the root of the nwnprc cvs module. Looks for dialog.tlk under tlk/.\n" +
"Reads the cls_??cr_*.2da files and updates cls_feat_*.2da, cls_????*.2da,\n" +
"feat.2da, iprp_feats.2da and spells.2da."
);
System.exit(0);
}
}

View File

@@ -0,0 +1,197 @@
package prc.utils;
import prc.autodoc.Data_2da;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import static prc.Main.err_pr;
import static prc.Main.spinner;
/**
* This class is meant for updating the class feat lists with the entries common to all
* base classes.
*
* @author Ornedan
*/
public class AllClassFeatUpdater {
private static boolean cropduplicates = false;
/**
* Ye olde main method.
* Takes as parameters the path of the 2da to use as update template and
*
* @param args [--help] | [-c] pathtoupdate2da targetpath+
*/
public static void main(String[] args) {
if (args.length == 0) readMe();
String cfabcPath = null;
List<String> paths = new ArrayList<String>();
// parse args
for (String param : args) {//[--help] | pathtoupdate2da targetpath+
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
case 'c':
cropduplicates = true;
break;
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
// It's a pathname
if (cfabcPath == null)
cfabcPath = param;
else
paths.add(param);
}
}
if (cfabcPath == null) {
err_pr.println("Error: You did not specify the location of cls_feat_allBaseClasses.2da\n");
readMe();
}
if (paths.size() == 0) {
err_pr.println("Error: You did not specify targets!\n");
readMe();
}
spinner.disable();
// Load and crop the template 2da
Data_2da source = Data_2da.load2da(cfabcPath);
int i = source.getEntryCount();
boolean passedEnd = false, passedBegin = false;
while (--i >= 0) {
//###cls_feat_allBaseClasses_BEGIN###
//###cls_feat_allBaseClasses_END###
if (!passedEnd) {
if (source.getEntry("FeatLabel", i).equals("###cls_feat_allBaseClasses_END###")) {
passedEnd = true;
continue;
} else
source.removeRow(i);
} else if (!passedBegin) {
if (source.getEntry("FeatLabel", i).equals("###cls_feat_allBaseClasses_BEGIN###"))
passedBegin = true;
} else {
source.removeRow(i);
}
}
// Parse the targets and update all files found
for (String path : paths) {
File target = new File(path);
File[] cls_feat2da;
if (target.isDirectory()) {
cls_feat2da = target.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.getName().toLowerCase().startsWith("cls_feat_") &&
file.getName().toLowerCase().endsWith(".2da");
}
});
} else if (target.getName().toLowerCase().startsWith("cls_feat_") &&
target.getName().toLowerCase().endsWith(".2da"))
cls_feat2da = new File[]{target};
else {
err_pr.println("Error: Parameter \"" + path + "\" is not a valid target!");
continue;
}
Arrays.sort(cls_feat2da);
for (File file : cls_feat2da) {
try {
update2da(source, Data_2da.load2da(file.getCanonicalPath()), file.getParentFile().getCanonicalPath());
} catch (Exception e) {
err_pr.printException(e);
}
}
}
}
private static void update2da(Data_2da updateSource, Data_2da target, String path) throws IOException {
// Find the beginning of replaceable area
int beginRow = -1, i = 0;
while (beginRow == -1 && i < target.getEntryCount()) {
if (target.getEntry("FeatLabel", i).equals("###cls_feat_allBaseClasses_BEGIN###"))
beginRow = i;
i++;
}
if (beginRow != -1) {
// Strip the lines to be replaced
while (beginRow < target.getEntryCount()) {
if (target.getEntry("FeatLabel", beginRow).equals("###cls_feat_allBaseClasses_END###")) {
target.removeRow(beginRow);
break;
} else
target.removeRow(beginRow);
}
// Handle cropping, if it is enabled
if (cropduplicates) {
// Make a copy of the update source, which can then be cropped
updateSource = (Data_2da) updateSource.clone();
cropDuplicates(updateSource, target);
}
for (i = 0; i < updateSource.getEntryCount(); i++) {
target.insertRow(beginRow + i, updateSource.getRow(i));
}
//System.out.println(target); System.exit(0);
target.save2da(path, true, true);
}
}
private static void cropDuplicates(Data_2da toCrop, Data_2da source) {
// Load the featIDs from source to a hashset
HashSet<Integer> featIDs = new HashSet<Integer>();
for (int i = 0; i < source.getEntryCount(); i++) {
try {
featIDs.add(Integer.parseInt(source.getEntry("FeatIndex", i)));
} catch (NumberFormatException nfe) {/* Ignore empty rows */}
}
// Loop over the crop target, removing rows whose FeatIndex is present in the source
for (int i = toCrop.getEntryCount() - 1; i >= 0; i--) {
try {
if (featIDs.contains(Integer.parseInt(toCrop.getEntry("FeatIndex", i))))
toCrop.removeRow(i);
} catch (NumberFormatException nfe) {/* Ignore empty rows */}
}
}
private static void readMe() {
System.out.println("Usage:\n" +
" [--help] | [-c] pathtoupdate2da targetpath+\n" +
"\n" +
" pathtoupdate2da path of the cls_feat_allBaseClasses.2da\n" +
" targetpath the path of a directory containing the cls_feat_*.2das to update\n" +
" or directly the path of a cls_feat_*.2da\n" +
"\n" +
" -c crop rows from the update source that contain featIDs present elsewhere\n" +
" in the target than in the updateable area\n" +
"\n" +
" --help prints this text\n"
);
System.exit(0);
}
}

Binary file not shown.

View File

@@ -0,0 +1,116 @@
package prc.utils;
import prc.autodoc.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* A tool for blanking ranges of 2da rows.
*
* @author Ornedan
*/
public class Blank2daRows {
/**
* Ye olde maine methode.
*
* @param args The program arguments, as usual.
* @throws Exception
*/
public static void main(String[] args) throws Exception {
if (args.length == 0) readMe();
String targetPath = null;
List<int[]> ranges = new ArrayList<int[]>();
for (String param : args) {//target B-E ... | [--help]
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
default:
System.err.println("Unknown parameter: " + c);
readMe();
}
}
}
} else
// If the target file hasn't been specified, assume this is it
if (targetPath == null)
targetPath = param;
else {
try {
if (!param.contains("-")) throw new Exception(); // Missing -
String[] rawNums = param.split("-");
if (rawNums.length != 2) throw new Exception(); // More than two numbers(?) in the string
int[] range = new int[]{Integer.parseInt(rawNums[0]), Integer.parseInt(rawNums[1])};
ranges.add(range);
} catch (Exception e) {
System.err.println("Malformed number pair:" + param);
System.exit(1);
}
}
}
// Load the 2da
Data_2da target = Data_2da.load2da(targetPath);
// Bounds checks
for (int[] range : ranges) {
if (range[0] > range[1]) { // Lower bound > upper bound
System.err.println("Lower bound > upper bound in pair: " + range[0] + "-" + range[1]);
System.exit(1);
}
if (range[0] < 0 || range[1] < 0 ||
range[0] >= target.getEntryCount() ||
range[1] >= target.getEntryCount()) { // Bound out of range
System.err.println("Bound out of range in pair: " + range[0] + "-" + range[1]);
System.exit(1);
}
}
// Do blanking
for (int[] range : ranges)
blankRows(target, range[0], range[1]);
// Save 2da back out
target.save2da(new File(targetPath).getParent(), true, true);
}
/**
* Does the row blanking - replaces the contents of each column in the affected row range with whatever
* is the default value in this 2da.
*
* @param target Data_2da to perform the operation on
* @param begin Beginning of the range to blank, inclusive
* @param end End of the range to blank, inclusive
*/
private static void blankRows(Data_2da target, int begin, int end) {
for (int i = begin; i <= end; i++) {
for (String label : target.getLabels())
target.setEntry(label, i, null);
}
}
private static void readMe() {
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
System.out.println("Usage:\n" +
" target B-E ... | [--help]\n" +
"\n" +
" This tool blanks rows in a 2da file, replacing the contents of given rows with\n" +
" either **** or the 2da-specific default value if such is specified.\n" +
"\n" +
" target Path to the 2da file to apply the blanking operation to\n" +
" B-E Row number pair, specifying which rows in the file to blank. Inclusive,\n" +
" for example 1000-1002 would blank rows 1000, 1001 and 1002. Multiple\n" +
" ranges may be specified.\n" +
"\n" +
" --help prints this text\n"
);
System.exit(0);
}
}

View File

@@ -0,0 +1,87 @@
package prc.utils;
import prc.autodoc.Main.TLKStore;
import prc.autodoc.Main.TwoDAStore;
import java.io.IOException;
/**
* A class that combines Scrollgen, UpdateDes and ScrollMerchantGen. For use during
* the build process to avoid loading the same 2da files several times.
*
* @author Ornedan
*/
public class BuildScrollHack {
/**
* Ye olde maine methode.
*
* @param args The arguments
* @throws IOException Just toss any exceptions encountered
*/
public static void main(String[] args) throws IOException {
if (args.length == 0) readMe();
String twoDAPath = null;
String tlkPath = null;
String outPath = null;
// parse args
for (String param : args) {//2dadir tlkdir outpath| [--help]
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
// It's a pathname
if (twoDAPath == null)
twoDAPath = param;
else if (tlkPath == null)
tlkPath = param;
else if (outPath == null)
outPath = param;
else {
System.out.println("Unknown parameter: " + param);
readMe();
}
}
}
// Load data
TwoDAStore twoDA = new TwoDAStore(twoDAPath);
TLKStore tlks = new TLKStore("dialog.tlk", "prc_consortium.tlk", tlkPath);
ScrollGen.doScrollGen(twoDA, twoDAPath, outPath);
UpdateDes.doUpdateDes(twoDA, twoDAPath);
ScrollMerchantGen.doScrollMerchantGen(twoDA, tlks, outPath);
}
/**
* Prints the use instructions for this program and kills execution.
*/
private static void readMe() {
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
System.out.println("Usage:\n" +
" java -jar prc.jar buildscrhack 2dadir tlkdir outpath| [--help]\n" +
"\n" +
"2dadir Path to a directory containing 2da files\n" +
"tlkdir Path to a directory containing dialog.tlk and prc_consortium.tlk\n" +
"outdir Path to the directory to save the new scroll xml files in\n" +
"\n" +
"--help prints this info you are reading\n" +
"\n" +
"\n" +
"A tool for automatically updating parts of des_crft_scrolls.2da and\n" +
"des_crft_spells.2da based on spells.2da\n"
);
System.exit(0);
}
}

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,128 @@
package prc.utils;
import prc.autodoc.Data_2da;
import java.io.File;
import java.io.FileWriter;
import java.util.Scanner;
import java.util.regex.Pattern;
public final class CodeGen {
private CodeGen() {
}
private static class Script implements Cloneable {
public String text,
name;
public Script(String text, String name) {
this.text = text;
this.name = name;
}
public Object clone() {
return new Script(text, name);
}
}
private static String prefix,
suffix,
template;
private static Data_2da[] data;
/**
* The main method, as usual.
*
* @param args do I really need to exapain this?
* @throws Exception this is a simple tool, just let all failures blow up
*/
public static void main(String[] args) throws Exception {
if (args.length == 0 || args[0].equals("--help") || args[0].equals("-?"))
readMe();
prefix = args[0];
suffix = args[1];
template = readTemplate(args[2]);
data = new Data_2da[args.length - 3];
for (int i = 0; i < data.length; i++)
data[i] = Data_2da.load2da(args[i + 3]);
doCreation(new Script(template, prefix), 0);
}
private static void readMe() {
System.out.println("Usage:\n" +
"\tjava CodeGen namePrefix nameSuffix templatePath 2daPaths...\n" +
"\n" +
"namePrefix\tprefix that all the resulting filenames will share\n" +
"nameSuffix\tsuffix that all the resulting filenames will share\n" +
"templatePath\tlocation of the template to use. May be absolute or relative\n" +
"2daPaths\tone or more 2da files to use fill the template with\n" +
"\n\n" +
"Places to replace in the template are marked with ~~~Identifier~~~. This will\n" +
"be replaced by entries from a column in one of the 2das labeled Identifier.\n" +
"This is case-insensitive.\n" +
"\n" +
"The 2da files must contain two columns labeled Suffix and Value.\n" +
"\n" +
"One script file will be generated for each possible combination of values from\n" +
"the 2das. They will automatically have file type identifier of .nss"
);
System.exit(0);
}
private static String readTemplate(String filePath) throws Exception {
Scanner reader = new Scanner(new File(filePath));
StringBuffer temp = new StringBuffer();
while (reader.hasNextLine()) temp.append(reader.nextLine() + "\n");
return temp.toString();
}
private static void doCreation(Script script, int depth) throws Exception {
if (depth == data.length) {
printScript(script);
return;
}
Script copy;
for (int i = 0; i < data[depth].getEntryCount(); i++) {
copy = (Script) script.clone();
for (String label : data[depth].getLabels())
copy.text = Pattern.compile("~~~" + label + "~~~", Pattern.CASE_INSENSITIVE)
.matcher(copy.text)
.replaceAll(data[depth].getEntry(label, i));
/*copy.text = script.text.replaceAll("~~~" + data[depth].getName() + "~~~",
data[depth].getEntry("Value", i));*/
copy.name += data[depth].getEntry("Suffix", i) != null ?
data[depth].getEntry("Suffix", i) :
"";
doCreation(copy, depth + 1);
}
}
private static void printScript(Script toPrint) throws Exception {
File target = new File(toPrint.name + suffix);
// Clean up old version if necessary
if (target.exists()) {
System.out.println("Deleting previous version of " + target.getName());
target.delete();
}
target.createNewFile();
// Creater the writer and print
FileWriter writer = new FileWriter(target, false);
writer.write(toPrint.text);
// Clean up
writer.flush();
writer.close();
}
}

Binary file not shown.

View File

@@ -0,0 +1,127 @@
package prc.utils;
import prc.autodoc.*;
import java.io.File;
public final class Data2daMerge {
private Data2daMerge() {
}
private static String source = "";
private static String merge = "";
private static String output = "";
/**
* The main method, as usual.
*
* @param args do I really need to explain this?
* @throws Exception this is a simple tool, just let all failures blow up
*/
public static void main(String[] args) throws Exception {
if (args.length < 3 || args[0].equals("--help") || args[0].equals("-?"))
readMe();
//check arguments for directories to use
source = args[0];
merge = args[1];
output = args[2];
//assemble arrays of filenames
File[] sourceFiles = new File(source).listFiles();
File[] mergeFiles = new File(merge).listFiles();
//define some things for speed
File sourceFile;
File mergeFile;
Data_2da sourceData;
Data_2da mergeData;
Data_2da outputData;
for (int i = 0; i < sourceFiles.length; i++) {
sourceFile = sourceFiles[i];
//ignore non-2da files
if (sourceFile.getPath().endsWith(".2da")) {
String sourceFilename = sourceFile.getName().substring(0, sourceFile.getName().length() - 4);
//loop over the merge files to find one that matches the source
boolean matchFound = false;
for (int j = 0; j < mergeFiles.length; j++) {
mergeFile = mergeFiles[j];
//ignore non-2da files
if (mergeFile.getPath().endsWith(".2da")) {
String mergeFilename = mergeFile.getName().substring(0, mergeFile.getName().length() - 4);
//its a match
if (mergeFilename.equals(sourceFilename)) {
matchFound = true;
String sourcePath = sourceFile.getAbsolutePath();
String mergePath = mergeFile.getAbsolutePath();
sourceData = Data_2da.load2da(sourcePath, true);
mergeData = Data_2da.load2da(mergePath, true);
outputData = Data_2da.load2da(sourcePath, true);
//Data_2da outputData = sourceData;
//get the smallest number of rows
int rowCount = sourceData.getEntryCount();
if (rowCount > mergeData.getEntryCount())
rowCount = mergeData.getEntryCount();
//check the columns match
//or at least the number of them
int colCount = sourceData.getLabels().length;
if (mergeData.getLabels().length != colCount) {
System.out.println("Number of columns does not match!");
} else {
//loop over the overlapping rows
for (int row = 0; row < rowCount; row++) {
for (int col = 0; col < colCount; col++) {
String columnName = sourceData.getLabels()[col];
String sourceEntry = sourceData.getEntry(columnName, row);
String mergeEntry = mergeData.getEntry(columnName, row);
if (!mergeEntry.equals("****")
&& !mergeEntry.equals(sourceEntry)) {
outputData.setEntry(columnName, row, mergeEntry);
//made a change, log it
System.out.println(sourceFilename + " : " + columnName + "," + row + " " + sourceEntry + " -> " + mergeEntry);
}
}
}
//check if merge has extra rows
if (rowCount < mergeData.getEntryCount()) {
for (int row = rowCount; row < mergeData.getEntryCount(); row++) {
outputData.appendRow();
//copy the extra rows to the output
for (int col = 0; col < colCount; col++) {
String columnName = sourceData.getLabels()[col];
String mergeEntry = mergeData.getEntry(columnName, row);
if (!mergeEntry.equals("****")) {
outputData.setEntry(columnName, row, mergeEntry);
//made a change, log it
System.out.println(sourceFilename + " : " + columnName + "," + row + " null -> " + mergeEntry);
}
}
}
}
}
//finished assembling the output file, write it
//this overwrites the target file
outputData.save2da(output, true, true);
}
}
}
//end of file loop for
}
}
}
private static void readMe() {
System.out.println("Usage:\n" +
"\tjava 2damerge source merge output\n" +
"\n" +
"This application is designed to take all the 2DA\n" +
"files in the source directory, merge them with the\n" +
"2DA files in the merge directory, and output the\n" +
"edited versions to the output directory"
);
System.exit(0);
}
}

View File

@@ -0,0 +1,127 @@
package prc.utils;
import prc.autodoc.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeMap;
import static prc.Main.err_pr;
/**
* Checks the given column in the given 2da for duplicate entries.
*
* @author Ornedan
*/
public class Duplicate2daEntryDetector {
private static boolean ignoreCase = false;
private static boolean accountEmpty = false;
/**
* Ye olde main.
*
* @param args
*/
public static void main(String[] args) {
if (args.length == 0) readMe();
String filePath = null;
ArrayList<String> labels = new ArrayList<String>();
// parse args
for (String param : args) {//[--help] | [-i] pathof2da columnlabel+
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
case 'c':
ignoreCase = true;
break;
case 'e':
accountEmpty = true;
break;
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
// It's a pathname
if (filePath == null)
filePath = param;
else
labels.add(param);
}
}
// Load the 2da
Data_2da file = Data_2da.load2da(filePath);
// Test the columns
for (String label : labels) {
TreeMap<String, ArrayList<Integer>> duplicates = new TreeMap<String, ArrayList<Integer>>();
// Examine column
HashMap<String, Integer> entries = new HashMap<String, Integer>();
String value;
for (int i = 0; i < file.getEntryCount(); i++) {
value = accountForCase(file, label, i);
// Do not account for empty entries unless requested
if (value.equals("****") && !accountEmpty)
continue;
if (!entries.containsKey(value))
entries.put(value, i);
else {
// If no duplicates of this value have yet been found, init the arraylist for it
if (!duplicates.containsKey(value)) {
duplicates.put(value, new ArrayList<Integer>());
duplicates.get(value).add(entries.get(value));
}
// Either way, add the current row to the list of duplicates
duplicates.get(value).add(i);
}
}
for (ArrayList<Integer> duprows : duplicates.values()) {
StringBuffer toPrint = new StringBuffer(file.getName() + ": duplicate " + label + " on rows");
boolean first = true;
for (int row : duprows) {
if (!first)
toPrint.append(',');
else
first = false;
toPrint.append(" " + row);
}
err_pr.println(toPrint.toString());
}
}
}
private static String accountForCase(Data_2da data, String label, int index) {
return ignoreCase ?
data.getEntry(label, index).toLowerCase() :
data.getEntry(label, index);
}
private static void readMe() {
System.out.println("Usage:\n" +
" [--help] | [-ce] pathof2da columnlabel+\n" +
"\n" +
" pathof2da path of the 2da to check\n" +
" columnlabel label of a column to test for duplicate entries\n" +
"\n" +
" -c ignores case of the entries testes for duplicacy\n" +
" -e does not ignore empty entries. ie, ****\n" +
"\n" +
" --help prints this text\n" +
"\n" +
"Unless -e is specified, empty entries (****) do not count as duplicates\n"
);
System.exit(0);
}
}

View File

@@ -0,0 +1,200 @@
package prc.utils;
import prc.autodoc.Data_2da;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* A class that performs some validation on spell.2da by checking if it contains duplicated
* subradial IDs.
* Also has capability to attempt to replace the duplicate IDs with unique ones selected
* automatically starting from a given index.
*/
public class DuplicateSubradials {
private static class DuplicateData {
int subnum;
//Set<Integer> indices = new HashSet<Integer>();
List<Integer> indices = new ArrayList<Integer>();
DuplicateData(int subnum) {
this.subnum = subnum;
}
}
/**
* Main method
*
* @param args The program arguments
*/
public static void main(String[] args) {
if (args.length == 0) readMe();
String pathtospells2da = null;
boolean fixduplicates = false;
int replacementstart = -1;
// parse args
for (String param : args) {//[--help] | [-f replacestart] pathtospells2da
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
case 'f':
fixduplicates = true;
break;
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
// The option to attempt fixing the duplicates is on and the first replacement number hasn't been given yet
if (fixduplicates == true && replacementstart == -1) {
try {
replacementstart = Integer.parseInt(param);
} catch (NumberFormatException e) {
System.out.println("replacestart value given is not numeric: " + param);
readMe();
}
if (replacementstart < 0 || replacementstart >= 0x10000) {
System.out.println("replacestart value given is not in valid range");
readMe();
}
}
// It's a pathname
else if (pathtospells2da == null)
pathtospells2da = param;
}
}
// Load the 2da to memory
Data_2da spells = Data_2da.load2da(pathtospells2da);
Map<Integer, Integer> subrads = new HashMap<Integer, Integer>(); // Map of subradial # to the first line it occurs on
Map<Integer, DuplicateData> duplicates = new HashMap<Integer, DuplicateData>();
String entry;
int subnum = 0;
// Parse through the 2da, looking for FeatID references that contain a subradial ID
for (int i = 0; i < spells.getEntryCount(); i++) {
entry = spells.getEntry("FeatID", i);
// Skip blanks
if (entry.equals("****")) continue;
try {
subnum = Integer.parseInt(entry);
} catch (NumberFormatException e) {
System.out.println("Corrupt value in FeatID on row " + i + ": " + entry);
continue;
}
// Skip non-subradial FeatIDs
if (subnum < 0x10000) continue;
subnum = subnum >>> 16;
if (subrads.containsKey(subnum)) {
if (!duplicates.containsKey(subnum))
duplicates.put(subnum, new DuplicateData(subnum));
duplicates.get(subnum).indices.add(i);
} else subrads.put(subnum, i);
}
// Print the results
int requiredtofix = 0;
for (DuplicateData dup : duplicates.values()) {
System.out.println("Duplicate subradial ID: " + dup.subnum + " first occurrence on row " + subrads.get(dup.subnum));
for (int i : dup.indices)
System.out.println("Duplicate subradial ID: " + dup.subnum + " on row " + i);
requiredtofix += dup.indices.size();
}
if (requiredtofix > 0)
System.out.println("\nNumber of new subradial IDs required to make all unique: " + requiredtofix);
if (fixduplicates && requiredtofix > 0) {
System.out.println("\n\nAttempting to fix.");
// Construct a list of the replacement subradial IDs
List<Integer> replacementlist = new ArrayList<Integer>();
int replacementid = replacementstart;
while (replacementlist.size() < requiredtofix) {
if (replacementid >= 0x10000) {// Make sure we don't exceed the bounds
System.out.println("Not enough free subradial IDs in the range from " + replacementstart + " to " + 0x10000 + "!");
System.exit(1);
}
// Pick ones not already in use
if (!subrads.containsKey(replacementid))
replacementlist.add(replacementid);
replacementid++;
}
// Loop over the duplicates, fixing as we go
Iterator<Integer> replacements = replacementlist.iterator();
int featid;
for (DuplicateData dup : duplicates.values()) {
for (int i : dup.indices) {
// Extract the base featID. Low 16 bits
featid = Integer.parseInt(spells.getEntry("FeatID", i)) & 0x0000FFFF;
// Insert new subradial number
subnum = replacements.next();
featid = featid | (subnum << 16);
// Store the new subradial'd featid in spells.2da
spells.setEntry("FeatID", i, Integer.toString(featid));
}
}
// Save the 2da
try {
spells.save2da((new File(pathtospells2da)).getParent(), true, true);
} catch (IOException e) {
System.err.println("Error while saving spells.2da!\n" + e);
System.exit(1);
}
// List the used subradial IDs
System.out.println("Subradial IDs used:");
Integer prev = null;
for (Integer subrad : replacementlist) {
// Detect if a new range is starting
if (prev == null || // Special case - just starting
subrad != (prev + 1) // There's a break in the series
) {
// Print the end of previous range
if (prev != null)
System.out.println(prev);
// Print the start of the new range
System.out.print(subrad + " - ");
}
// Update prev
prev = subrad;
}
// Print the end of the last range
System.out.println(prev);
}
}
private static void readMe() {
System.out.println("Usage:\n" +
" [--help] | [-f replacestart] pathtospells2da\n" +
"\n" +
" pathtospells2da path of the spells.2da to check\n" +
" replacestart the first subradial ID to replace duplicates\n" +
" with when fixing. optional, required if -f is set\n" +
"\n" +
" --help prints this text\n" +
" -f attempt to replace the duplicate subradial IDs with new\n" +
" unused IDs starting with the value given as replacestart\n" +
"\n" +
"\n" +
"Looks for duplicate subradial IDs in the FeatID column of the given\n" +
"spells.2da. May optionally attempt to replace the duplicates with unique\n" +
"values.\n"
);
System.exit(0);
}
}

Binary file not shown.

View File

@@ -0,0 +1,278 @@
package prc.utils;
import prc.autodoc.*;
import java.io.File;
import java.io.FileWriter;
import static prc.Main.verbose;
public final class ItempropMaker {
private ItempropMaker() {
}
private static Data_2da itempropdef2da;
private static Data_2da costtable2da;
private static Data_2da paramtable2da;
private static Data_2da[] cost2daarray;
private static Data_2da[] param12daarray;
private static StringBuilder xml;
public static void main(String[] args) throws Exception {
//load the 2das
itempropdef2da = Data_2da.load2da("2das" + File.separator + "itempropdef.2da", true);
costtable2da = Data_2da.load2da("2das" + File.separator + "iprp_costtable.2da", true);
paramtable2da = Data_2da.load2da("2das" + File.separator + "iprp_paramtable.2da", true);
cost2daarray = new Data_2da[costtable2da.getEntryCount()];
param12daarray = new Data_2da[paramtable2da.getEntryCount()];
for (int i = 0; i < cost2daarray.length; i++) {
cost2daarray[i] = Data_2da.load2da("2das" + File.separator + costtable2da.getBiowareEntry("Name", i) + ".2da", true);
}
for (int i = 0; i < param12daarray.length; i++) {
param12daarray[i] = Data_2da.load2da("2das" + File.separator + paramtable2da.getBiowareEntry("TableResRef", i) + ".2da", true);
}
//loop over each row
for (int itempropdef2darow = 85;
itempropdef2darow < itempropdef2da.getEntryCount();
itempropdef2darow++) {
if (itempropdef2da.getBiowareEntryAsInt("Name", itempropdef2darow) != 0) {
int type = itempropdef2darow;
int subtype;
int cost;
int param1;
if (itempropdef2da.getBiowareEntry("SubTypeResRef", type) == "")
subtype = 0;
else
subtype = 1;
if (itempropdef2da.getBiowareEntryAsInt("CostTableResRef", type) == 0)
cost = 0;
else
cost = 1;
if (itempropdef2da.getBiowareEntry("Param1ResRef", type) == "")
param1 = 0;
else
param1 = 1;
//loop over each subtype
if (subtype != 0) {
Data_2da subtype2da = Data_2da.load2da("2das" + File.separator + itempropdef2da.getBiowareEntry("SubTypeResRef", type) + ".2da", true);
for (int subtypeID = 0; subtypeID < subtype2da.getEntryCount(); subtypeID++) {
//loop over the param1s, if applicable
//look if there is a column for it
boolean subtypeparam1columnexists = false;
String[] columnlabels = subtype2da.getLabels();
for (int i = 0; i < columnlabels.length; i++) {
if (columnlabels[i] == "Param1ResRef")
subtypeparam1columnexists = true;
}
if (subtypeparam1columnexists
&& subtype2da.getBiowareEntry("Param1ResRef", subtypeID) == "") {
param1 = 1;
} else {
if (itempropdef2da.getBiowareEntry("Param1ResRef", type) == "")
param1 = 0;
else
param1 = 2;
}
if (param1 != 0) {
int param1tableid = 0;
if (param1 == 2)
param1tableid = itempropdef2da.getBiowareEntryAsInt("Param1ResRef", type);
else if (param1 == 1)
param1tableid = subtype2da.getBiowareEntryAsInt("Param1ResRef", subtypeID);
Data_2da param12da = param12daarray[param1tableid];
for (int param1ID = 0; param1ID < param12da.getEntryCount(); param1ID++) {
if (cost != 0) {
int costtable = itempropdef2da.getBiowareEntryAsInt("CostTableResRef", type);
Data_2da cost2da = cost2daarray[costtable];
//has type, subtype, param1, and cost
write(type, subtypeID, param1tableid, param1ID, costtable, cost2da.getEntryCount());
} else {
//no cost
//has type, subtype, and param1
write(type, subtypeID, param1tableid, param1ID, -1, -1);
}
}
} else {
//no param1
if (cost != 0) {
int costtable = itempropdef2da.getBiowareEntryAsInt("CostTableResRef", type);
Data_2da cost2da = cost2daarray[costtable];
//has type, subtype, and cost
write(type, subtypeID, -1, -1, costtable, cost2da.getEntryCount());
} else {
//no cost
//has type, and subtype
write(type, subtypeID, -1, -1, -1, -1);
}
}
}
} else {
//no subtype
if (param1 != 0) {
int param1tableid = itempropdef2da.getBiowareEntryAsInt("Param1ResRef", type);
Data_2da param12da = param12daarray[param1tableid];
for (int param1ID = 0; param1ID < param12da.getEntryCount(); param1ID++) {
if (cost != 0) {
int costtable = itempropdef2da.getBiowareEntryAsInt("CostTableResRef", type);
Data_2da cost2da = cost2daarray[costtable];
//has type, param1, and cost
write(type, -1, param1tableid, param1ID, costtable, cost2da.getEntryCount());
} else {
//no cost
//has type, and param1
write(type, -1, param1tableid, param1ID, -1, -1);
}
}
} else {
//no param1
if (cost != 0) {
int costtable = itempropdef2da.getBiowareEntryAsInt("CostTableResRef", type);
Data_2da cost2da = cost2daarray[costtable];
//has type, and cost
write(type, -1, -1, -1, costtable, cost2da.getEntryCount());
} else {
//no cost
//has type
write(type, -1, -1, -1, -1, -1);
}
}
}
}
}
}
/*
<!-- This file was generated by the NWNTools GFF to XML writer.
http://nwntools.sf.net/
** Do not hand edit unless you know what you are doing. **
-->
- <gff name="master111.uti" type="UTI" version="V3.2">
- <struct id="-1">
<element name="TemplateResRef" type="11" value="master111" />
<element name="BaseItem" type="5" value="111" />
<element name="LocalizedName" type="12" value="83617" />
<element name="Description" type="12" value="-1" />
<element name="DescIdentified" type="12" value="-1" />
<element name="Tag" type="10" value="master111" />
<element name="Charges" type="0" value="0" />
<element name="Cost" type="4" value="2" />
<element name="Stolen" type="0" value="0" />
<element name="StackSize" type="2" value="1" />
<element name="Plot" type="0" value="0" />
<element name="AddCost" type="4" value="0" />
<element name="Identified" type="0" value="1" />
<element name="Cursed" type="0" value="0" />
<element name="ModelPart1" type="0" value="11" />
<element name="ModelPart2" type="0" value="11" />
<element name="ModelPart3" type="0" value="11" />
- <element name="PropertiesList" type="15">
<struct id="0" >
<element name="PropertyName" type="2" value="85" />
<element name="Subtype" type="2" value="6" />
<element name="CostTable" type="0" value="28" />
<element name="CostValue" type="2" value="0" />
<element name="Param1" type="0" value="0" />
<element name="Param1Value" type="0" value="0" />
<element name="ChanceAppear" type="0" value="100" />
</struct>
- <struct id="0">
<element name="PropertyName" type="2" value="12" />
<element name="Subtype" type="2" value="37" />
<element name="CostTable" type="0" value="0" />
<element name="CostValue" type="2" value="0" />
<element name="Param1" type="0" value="255" />
<element name="Param1Value" type="0" value="0" />
<element name="ChanceAppear" type="0" value="100" />
</struct>
</element>
<element name="PaletteID" type="0" value="47" />
<element name="Comment" type="10" value="" />
</struct>
</gff>
*/
private static void write(int type, int subtype, int param1table, int param1value, int costtable,
int costmax) throws Exception {
xml = new StringBuilder(0xFFFFF);
//assemble the resref/tag
String resref = "prc_ip" + type;
if (subtype != -1)
resref += "_" + subtype;
if (param1value != -1)
resref += "_" + param1value;
//sanity checks
if (param1value == -1)
param1value = 0;
if (param1table == -1)
param1table = 255;
if (subtype == -1)
subtype = 0;
if (costtable == -1)
costtable = 0;
if (costmax == -1)
costmax = 0;
//output stuff
//header things first
xml.append("<gff name=\"" + resref + ".uti\" type=\"UTI \" version=\"V3.2\">\n");
xml.append(" <struct id=\"-1\">\n");
xml.append(" <element name=\"TemplateResRef\" type=\"11\" value=\"" + resref + "\" />\n");
xml.append(" <element name=\"BaseItem\" type=\"5\" value=\"78\" />\n");
xml.append(" <element name=\"LocalizedName\" type=\"12\" value=\"-1\" >\n");
xml.append(" <localString languageId=\"0\" value=\"0\" />\n");
xml.append(" </element>\n");
xml.append(" <element name=\"Description\" type=\"12\" value=\"-1\" />\n");
xml.append(" <element name=\"DescIdentified\" type=\"12\" value=\"-1\" />\n");
xml.append(" <element name=\"Tag\" type=\"10\" value=\"" + resref + "\" />\n");
xml.append(" <element name=\"Charges\" type=\"0\" value=\"0\" />\n");
xml.append(" <element name=\"Cost\" type=\"4\" value=\"2\" />\n");
xml.append(" <element name=\"Stolen\" type=\"0\" value=\"0\" />\n");
xml.append(" <element name=\"StackSize\" type=\"2\" value=\"1\" />\n");
xml.append(" <element name=\"Plot\" type=\"0\" value=\"0\" />\n");
xml.append(" <element name=\"AddCost\" type=\"4\" value=\"0\" />\n");
xml.append(" <element name=\"Identified\" type=\"0\" value=\"1\" />\n");
xml.append(" <element name=\"Cursed\" type=\"0\" value=\"0\" />\n");
xml.append(" <element name=\"ModelPart1\" type=\"0\" value=\"1\" />\n");
xml.append(" <element name=\"PropertiesList\" type=\"15\">\n");
//loop over the itemproperties
for (int i = 0; i < costmax; i++) {
xml.append(" <struct id=\"0\" >\n");
xml.append(" <element name=\"PropertyName\" type=\"2\" value=\"" + type + "\" />\n");
xml.append(" <element name=\"Subtype\" type=\"2\" value=\"" + subtype + "\" />\n");
xml.append(" <element name=\"CostTable\" type=\"0\" value=\"" + costtable + "\" />\n");
xml.append(" <element name=\"CostValue\" type=\"2\" value=\"" + i + "\" />\n");
xml.append(" <element name=\"Param1\" type=\"0\" value=\"" + param1table + "\" />\n");
xml.append(" <element name=\"Param1Value\" type=\"0\" value=\"" + param1value + "\" />\n");
xml.append(" <element name=\"ChanceAppear\" type=\"0\" value=\"100\" />\n");
xml.append(" </struct>\n");
}
//footer stuff
xml.append(" </element>\n");
//this is set to 99 so it will not appear in the palette :)
xml.append(" <element name=\"PaletteID\" type=\"0\" value=\"99\" />\n");
xml.append(" <element name=\"Comment\" type=\"10\" value=\"\" />\n");
xml.append(" </struct>\n");
xml.append("</gff>");
File target = new File("xml_temp" + File.separator + resref + ".uti.xml");
// Clean up old version if necessary
if (target.exists()) {
if (verbose) System.out.println("Deleting previous version of " + target.getName());
target.delete();
}
if (verbose) System.out.println("Writing brand new version of " + target.getName());
target.createNewFile();
// Creater the writer and print
FileWriter writer = new FileWriter(target, true);
writer.write(xml.toString());
// Clean up
writer.flush();
writer.close();
// Force garbage collection
System.gc();
}
}

View File

@@ -0,0 +1,137 @@
package prc.utils;
import prc.autodoc.*;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import static prc.Main.err_pr;
/**
* A class for generating Leto XML files from 2da and TLK.
*
* @author Ornedan
*/
public final class LetoListsGenerator {
private final Data_TLK defaultTLK;
private final Data_TLK userTLK;
private LetoListsGenerator(Data_TLK defaultTLK, Data_TLK userTLK) {
this.defaultTLK = defaultTLK;
this.userTLK = userTLK;
}
private String getTLK(int num) {
return num < 0x01000000 ? defaultTLK.getEntry(num) : userTLK.getEntry(num);
}
/**
* Prints the given 2da into a Leto XML file.
*
* @param toPrint Data_2da containing the 2da to print
* @param nameColumn The name of the column that defines the name of the entry in TLK
* @param os The outputstream to write the xml to
*/
public void printData2daAsLetoXML(Data_2da toPrint, String nameColumn, OutputStream os) {
PrintStream ps = new PrintStream(os, true);
StringBuilder sb;
// Print header
ps.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
ps.println("<LetoList comment=\"Created with a Java-based tool\">");
// Print contents
for (int i = 0; i < toPrint.getEntryCount(); i++) {
if (!toPrint.getBiowareEntry(nameColumn, i).equals("")) {
sb = new StringBuilder(" <item index=\"" + i + "\" ");
for (String column : toPrint.getLabels()) {
sb.append(column.toLowerCase() + "=\"" + toPrint.getBiowareEntry(column, i) + "\" ");
}
if (!getTLK(Integer.parseInt(toPrint.getBiowareEntry(nameColumn, i))).equals(""))
sb.append(">" + getTLK(Integer.parseInt(toPrint.getBiowareEntry(nameColumn, i))) + "</item>");
else
sb.append("/>");
ps.println(sb);
}
}
// Print closing tag
ps.println("</LetoList>");
ps.flush();
ps.close();
}
/**
* Ye olde main methode.
*
* @param args The parameters
* @throws Throwable
*/
public static void main(String[] args) throws Throwable {
if (args.length == 0) readMe();
String defaultTLKPath = null;
String userTLKPath = null;
ArrayList<String> twoDANames = new ArrayList<String>();
ArrayList<String> columnNames = new ArrayList<String>();
for (String param : args) {//[-crmnqs] file... | -
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
default:
err_pr.println("Error: Unknown parameter: " + c);
readMe();
}
}
}
} else {
if (defaultTLKPath == null)
defaultTLKPath = param;
else if (userTLKPath == null)
userTLKPath = param;
else {
if (twoDANames.size() == columnNames.size())
twoDANames.add(param);
else
columnNames.add(param);
}
}
}
if (defaultTLKPath == null || userTLKPath == null || twoDANames.size() != columnNames.size()) {
System.err.println("Error! Missing parameters");
readMe();
}
LetoListsGenerator llg = new LetoListsGenerator(new Data_TLK(defaultTLKPath), new Data_TLK(userTLKPath));
Data_2da twoDA;
for (int i = 0; i < twoDANames.size(); i++) {
twoDA = Data_2da.load2da(twoDANames.get(i));
llg.printData2daAsLetoXML(twoDA, columnNames.get(i), new FileOutputStream("nwn-" + twoDA.getName().toLowerCase() + ".xml"));
}
}
private static void readMe() {
System.out.println("Usage:\n" +
" pathToDefaultTLK pathToUserTLK [pathTo2da namecolumn]+\n" +
"\n" +
" --help prints this text\n" +
"\n" +
"\n" +
"\n" +
"Creates xml files for use with Leto editor based on the given files.\n" +
"Requires default.tlk, an user tlk and any numbers of 2da and column\n" +
"name pairs. The column name is used for fetching the name of the line\n" +
"from TLK.\n"
);
System.exit(0);
}
}

Binary file not shown.

View File

@@ -0,0 +1,94 @@
package prc.utils;
import prc.Main;
import prc.autodoc.Data_2da;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
/**
* A class that lists the unique entries found in the given column of the given 2da file.
*
* @author Ornedan
*/
public class List2daEntries {
/**
* Ye olde main.
*
* @param args Program arguments
*/
public static void main(String[] args) {
if (args.length == 0) readMe();
String filePath = null;
ArrayList<String> labels = new ArrayList<String>();
boolean quiet = false;
// parse args
for (String param : args) {//[--help] | pathof2da columnlabel+
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
case 'q':
quiet = true;
break;
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
// It's a pathname
if (filePath == null)
filePath = param;
else
labels.add(param);
}
}
if (quiet) {
Main.verbose = false;
Main.spinner.disable();
}
// Load the 2da
Data_2da file = Data_2da.load2da(filePath);
// Loop over the columns
for (String label : labels) {
Set<String> entries = new HashSet<String>();
String value;
for (int i = 0; i < file.getEntryCount(); i++) {
value = file.getEntry(label, i);
entries.add(value);
}
StringBuffer toPrint = new StringBuffer(quiet ? "" : (file.getName() + ": entries on column " + label + "\n"));
for (String entry : entries) {
toPrint.append(entry + "\n");
}
System.out.println(toPrint.toString());
}
}
private static void readMe() {
System.out.println("Usage:\n" +
" [--help] | [-q] pathof2da columnlabel+\n" +
"\n" +
" pathof2da path of the 2da to check\n" +
" columnlabel label of a column to list entries of\n" +
"\n" +
" -q silent mode. Only prints the results" +
" --help prints this text\n"
);
System.exit(0);
}
}

Binary file not shown.

View File

@@ -0,0 +1,110 @@
package prc.utils;
import prc.autodoc.Data_2da;
import java.util.TreeSet;
/**
* A class that parses spells.2da and lists used subradial feat ID ranges.
*
* @author Ornedan
*/
public class ListSubradials {
/**
* Main method
*
* @param args The program arguments
*/
public static void main(String[] args) {
if (args.length == 0) readMe();
String pathtospells2da = null;
// parse args
for (String param : args) {//[--help] | pathtospells2da
// Parameter parseage
if (param.startsWith("-")) {
if (param.equals("--help")) readMe();
else {
for (char c : param.substring(1).toCharArray()) {
switch (c) {
default:
System.out.println("Unknown parameter: " + c);
readMe();
}
}
}
} else {
// It's a pathname
if (pathtospells2da == null)
pathtospells2da = param;
}
}
// Load the 2da to memory
Data_2da feats = Data_2da.load2da(pathtospells2da);
TreeSet<Integer> subrads = new TreeSet<Integer>();
String entry;
int subnum;
// Parse through the 2da, looking for FeatID references that contain a subradial ID
for (int i = 0; i < feats.getEntryCount(); i++) {
entry = feats.getEntry("FeatID", i);
// Skip blanks
if (entry.equals("****")) continue;
try {
subnum = Integer.parseInt(entry);
} catch (NumberFormatException e) {
System.out.println("Corrupt value in FeatID on row " + i + ": " + entry);
continue;
}
// Skip non-subradial FeatIDs
if (subnum < 0x10000) continue;
subnum = subnum >>> 16;
subrads.add(subnum);
}
// Print the results
System.out.println("Subradial IDs used:");
if (subrads.isEmpty())
System.out.println("None");
else {
Integer prev = null;
for (Integer subrad : subrads) {
// Detect if a new range is starting
if (prev == null || // Special case - just starting
subrad != (prev + 1) // There's a break in the series
) {
// Print the end of previous range
if (prev != null)
System.out.println(prev);
// Print the start of the new range
System.out.print(subrad + " - ");
}
// Update prev
prev = subrad;
}
// Print the end of the last range
System.out.println(prev);
}
}
private static void readMe() {
// 0 1 2 3 4 5 6 7 8
// 12345678901234567890123456789012345678901234567890123456789012345678901234567890
System.out.println("Usage:\n" +
" [--help] | pathtospells2da\n" +
"\n" +
" pathtospells2da path of the spells.2da to check\n" +
"\n" +
" --help prints this text\n" +
"\n" +
"\n" +
"Lists used subradial ID ranges in the FeatID column of the given spells.2da\n"
);
System.exit(0);
}
}

Binary file not shown.

View File

@@ -0,0 +1,188 @@
package prc.utils;
import prc.autodoc.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Random;
public final class NPCEvolve {
private NPCEvolve() {
}
private static final int countAreas = 5;
private static final int countClasses = 11;
private static final int countPackages = 10;
public static Random rng = new Random();
/**
* The main method, as usual.
*
* @param args do I really need to explain this?
* @throws Exception this is a simple tool, just let all failures blow up
*/
public static void main(String[] args) throws Exception {
if (args.length < 3 || args[0].equals("--help") || args[0].equals("-?"))
readMe();
String logFilePath = args[0];
String packageFilePath = args[1];
String outputFilePath = args[2];
//this is the layout of the data string
//## id class0pack1 class0pack2 ... class10pack9 class10pack10 ##
//get the file
File logFile = new File(logFilePath);
BufferedReader reader = new BufferedReader(new FileReader(logFile));
//parse it
String line = reader.readLine();
int[][][] dataset = new int[countAreas][countClasses][countPackages];
int classID = 0;
int packageID = 0;
int areaID = 0;
int score = 0;
int countArea = 0;
while (line != null) {
if (line.matches("## ([0-9]*) [0-9 ]* ##")) {
areaID = new Integer(line.substring(4, 6));
//remove the +1 offset
areaID--;
//store highest area value
if (areaID > countArea)
countArea = areaID;
classID = 0;
packageID = 0;
line = line.substring(7, line.length() - 3);
String[] splitline = line.split(" ");
for (int i = 0; i < splitline.length; i++) {
score = new Integer(splitline[i]);
dataset[areaID][classID][packageID] = new Integer(splitline[i]);
//System.out.println("dataset["+areaID+"]["+classID+"]["+packageID+"] = "+dataset[areaID][classID][packageID]);
//move to next slot
packageID++;
if (packageID >= countPackages) {
packageID -= countPackages;
classID++;
if (classID >= countClasses)
classID -= countClasses;
}
}
}
line = reader.readLine();
}
//At this point, we have a big dataset with the most recent
//scores for each area for each class/package combination
//First step is to consolidate this to an average
int[][] data = new int[countClasses][countPackages];
for (classID = 0; classID < countClasses; classID++) {
for (packageID = 0; packageID < countPackages; packageID++) {
//reset score to zero
score = 0;
for (areaID = 0; areaID < countArea; areaID++) {
score += dataset[areaID][classID][packageID];
}
data[classID][packageID] = score / countArea;
System.out.println("data[" + classID + "][" + packageID + "] = " + data[classID][packageID]);
}
}
//now we know what each packageset scored, go through each class and cross the best 2 to
//make the next generation
for (classID = 0; classID < countClasses; classID++) {
int packageMother = 0;
int packageFather = 0;
int scoreMother = 0;
int scoreFather = 0;
for (packageID = 0; packageID < countPackages; packageID++) {
score = data[classID][packageID];
if (score > scoreMother) {
scoreFather = scoreMother;
scoreMother = score;
packageFather = packageMother;
packageMother = packageID;
} else if (score > scoreFather) {
scoreFather = score;
packageFather = packageID;
}
}
//now we have a mother and a father packageset
//remember, these are sets of packages, not the actual packages themselves
//load the 2das
String filename = "";
filename = "evopset_" + classID + "_";
//if(packageMother<10)
// filename += "0";
filename += "" + packageMother;
Data_2da motherpackset2da = Data_2da.load2da(packageFilePath + File.separator + filename + ".2da");
filename = "evopset_" + classID + "_";
//if(packageFather<10)
// filename += "0";
filename += "" + packageFather;
Data_2da fatherpackset2da = Data_2da.load2da(packageFilePath + File.separator + filename + ".2da");
//create the output 2das
for (int i = 0; i < countPackages; i++) {
filename = "evopset_" + classID + "_";
//if(i<10)
// filename += "0";
filename += i;
Data_2da offspringpackset2da = new Data_2da(filename);
offspringpackset2da.addColumn("PackOffset");
int parentToUse = 1;
for (int row = 0; row < 40; row++) {
offspringpackset2da.appendRow();
//1 in 10 chance to flip between parents
if (rng.nextInt(10) == 0) {
if (parentToUse == 1)
parentToUse = 2;
else if (parentToUse == 2)
parentToUse = 1;
}
if (parentToUse == 1) {
//copy from mother
String value = motherpackset2da.getEntry("PackOffset", row);
//mutation chance
if (rng.nextInt(10) == 0) {
value = "" + (rng.nextInt(10) + 1);
}
offspringpackset2da.setEntry("PackOffset", row, value);
} else if (parentToUse == 2) {
//copy from father
String value = fatherpackset2da.getEntry("PackOffset", row);
//mutation chance
if (rng.nextInt(10) == 0) {
value = "" + (rng.nextInt(10) + 1);
}
offspringpackset2da.setEntry("PackOffset", row, value);
}
}
//now write it to the disk
offspringpackset2da.save2da(outputFilePath);
}
}
}
//pastebin for later ;)
//first thing is load the overall packages.2da file
//Data_2da packages2da = Data_2da.load2da(packageFilePath+File.separator+"packages.2da");
/*
//get the real package IDs
packageMother += (countPackages*classID);
packageFather += (countPackages*classID);
//EvoPSP
//EvoPFTBard01
//EvoPSP*/
private static void readMe() {
System.out.println("Usage:\n" +
"\tjava npcevol logfile packages\n" +
"\n" +
"This application is designed to take a logfile \n" +
"and parse it to extract relevant data.\n" +
"Then it will take some package 2da files\n" +
"and mutate them appropriately"
);
System.exit(0);
}
}

Some files were not shown because too many files have changed in this diff Show More