315 lines
17 KiB
Java
315 lines
17 KiB
Java
package prc.utils;
|
|
|
|
import prc.autodoc.*;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
|
|
import static prc.Main.err_pr;
|
|
import static prc.autodoc.Main.TwoDAStore;
|
|
|
|
/**
|
|
* Performs a bunch of cross-reference tests on 2das.
|
|
*
|
|
* @author Ornedan
|
|
*/
|
|
public class Validator {
|
|
private static TwoDAStore twoDA;
|
|
private static boolean pedantic = false;
|
|
|
|
/**
|
|
* Ye olde maine methode.
|
|
*
|
|
* @param args The arguments
|
|
*/
|
|
public static void main(String[] args) {
|
|
if (args.length == 0) readMe();
|
|
String twoDAPath = null;
|
|
String tlkPath = null;
|
|
|
|
// parse args
|
|
for (String param : args) {//2dadir tlkdir | [--help]
|
|
// Parameter parseage
|
|
if (param.startsWith("-")) {
|
|
if (param.equals("--help")) readMe();
|
|
else {
|
|
for (char c : param.substring(1).toCharArray()) {
|
|
switch (c) {
|
|
case 'p':
|
|
pedantic = true;
|
|
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 {
|
|
System.out.println("Unknown parameter: " + param);
|
|
readMe();
|
|
}
|
|
}
|
|
}
|
|
|
|
twoDA = new TwoDAStore(twoDAPath);
|
|
|
|
doSpellsAndDes2dasTest(twoDAPath);
|
|
|
|
doSpellsAndIprpSpellsTest(twoDAPath);
|
|
|
|
doDesAndIprpTest(twoDAPath);
|
|
}
|
|
|
|
private static void doDesAndIprpTest(String twoDAPath) {
|
|
Data_2da iprp_spells = twoDA.get("iprp_spells"),
|
|
des_crft_spells = twoDA.get("des_crft_spells");
|
|
|
|
// For each des_crft_spells entry, see if it points at the lowest CasterLvl entry
|
|
Map<Integer, Tuple<Integer, Integer>> lowestIndex = new HashMap<Integer, Tuple<Integer, Integer>>(); // Map of spells.2da index -> (iprp_spells.2da index, CasterLvl value)
|
|
int spellID, casterLvl;
|
|
for (int i = 0; i < iprp_spells.getEntryCount(); i++) {
|
|
if (!iprp_spells.getEntry("SpellIndex", i).equals("****")) { // Only lines that are connected to spells.2da are scanned
|
|
try {
|
|
spellID = Integer.parseInt(iprp_spells.getEntry("SpellIndex", i));
|
|
} catch (NumberFormatException e) {
|
|
// Logged already, just skip
|
|
continue;
|
|
}
|
|
try {
|
|
casterLvl = Integer.parseInt(iprp_spells.getEntry("CasterLvl", i));
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Error: Non-number value in iprp_spells.2da CasterLvl column on line " + i + ": " + iprp_spells.getEntry("CasterLvl", i));
|
|
continue;
|
|
}
|
|
|
|
if (lowestIndex.get(spellID) == null || lowestIndex.get(spellID).e2 > casterLvl)
|
|
lowestIndex.put(spellID, new Tuple<Integer, Integer>(i, casterLvl));
|
|
}
|
|
}
|
|
for (int i = 0; i < des_crft_spells.getEntryCount(); i++) {
|
|
if (!des_crft_spells.getEntry("IPRP_SpellIndex", i).equals("****")) {
|
|
if (lowestIndex.get(i) == null)
|
|
err_pr.println("Error: Error: des_crft_spells.2da IPRP_SpellIndex defined for spell " + i + ", but no matching iprp_spells.2da entries exist");
|
|
try {
|
|
if (lowestIndex.get(i).e1 != Integer.parseInt(des_crft_spells.getEntry("IPRP_SpellIndex", i)))
|
|
err_pr.println("Error: Warning: des_crft_spells.2da IPRP_SpellIndex entry on line " + i + " points at iprp_spells.2da entry with non-lowest CasterLvl value: " + des_crft_spells.getEntry("IPRP_SpellIndex", i) + " (lowest is on line: " + lowestIndex.get(i).e1 + ")");
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Error: Non-number value in des_crft_spells.2da IPRP_SpellIndex column on line " + i + ": " + iprp_spells.getEntry("SpellIndex", i));
|
|
}
|
|
} else if (lowestIndex.get(i) != null)
|
|
err_pr.println("Error: Error: iprp_spells.2da entry defined for spell " + i + ", but des_crft_spells.2da IPRP_SpellIndex is not");
|
|
}
|
|
}
|
|
|
|
private static void doSpellsAndIprpSpellsTest(String twoDAPath) {
|
|
Data_2da spells = twoDA.get("spells"),
|
|
iprp_spells = twoDA.get("iprp_spells");
|
|
|
|
// For each iprp_spells entry, make sure InnateLevel matches spells Innate
|
|
for (int i = 0; i < iprp_spells.getEntryCount(); i++) {
|
|
if (!iprp_spells.getEntry("SpellIndex", i).equals("****")) { // Only lines that are connected to spells.2da are scanned
|
|
try {
|
|
if (!iprp_spells.getEntry("InnateLvl", i).equals(spells.getEntry("Innate", Integer.parseInt(iprp_spells.getEntry("SpellIndex", i))))
|
|
&&
|
|
!(spells.getEntry("Innate", Integer.parseInt(iprp_spells.getEntry("SpellIndex", i))).equals("0") &&
|
|
iprp_spells.getEntry("InnateLvl", i).equals("0.5")
|
|
)
|
|
)
|
|
err_pr.println("Error: Warning: Differing Innate and InnateLvl among spells.2da and iprp_spells.2da on iprp_spells.2da line " + i + ": " + "(" + spells.getEntry("Innate", Integer.parseInt(iprp_spells.getEntry("SpellIndex", i))) + "," + iprp_spells.getEntry("InnateLvl", i) + ")");
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Error: Non-number value in iprp_spells.2da SpellIndex column on line " + i + ": " + iprp_spells.getEntry("SpellIndex", i));
|
|
}
|
|
}
|
|
}
|
|
|
|
// For each iprp_spells entry, if GeneralUse is 1, check whether PotionUse and WandUse obey the standard level constraints
|
|
if (pedantic) {
|
|
int level;
|
|
boolean targetSelf;
|
|
for (int i = 0; i < iprp_spells.getEntryCount(); i++) {
|
|
if (!iprp_spells.getEntry("SpellIndex", i).equals("****")) { // Only lines that are connected to spells.2da are scanned
|
|
if (!iprp_spells.getEntry("GeneralUse", i).equals("****") &&
|
|
!iprp_spells.getEntry("GeneralUse", i).equals("0")) {
|
|
try {
|
|
level = Integer.parseInt(iprp_spells.getEntry("InnateLvl", i));
|
|
} catch (NumberFormatException e) {
|
|
if (iprp_spells.getEntry("InnateLvl", i).equals("0.5"))
|
|
level = 0;
|
|
else {
|
|
err_pr.println("Error: Error: Non-number value in iprp_spells.2da InnateLvl column on line " + i + ": " + iprp_spells.getEntry("InnateLvl", i));
|
|
continue;
|
|
}
|
|
}
|
|
try {
|
|
targetSelf =
|
|
(Integer.parseInt(spells.getEntry("TargetType",
|
|
Integer.parseInt(iprp_spells.getEntry("SpellIndex", i)))
|
|
.substring(2),
|
|
16)
|
|
& 0x1) == 1;
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Error: Non-number value among iprp_spells.2da SpellIndex and spells.2da TargetType on iprp_spells.2da line " + i);
|
|
continue;
|
|
}
|
|
|
|
if (iprp_spells.getEntry("PotionUse", i).equals("0") && targetSelf && level <= 3)
|
|
err_pr.println("Error: Warning: PotionUse 0 in iprp_spells.2da when spell of 3rd level or less and self-targetable on line " + i);
|
|
if (iprp_spells.getEntry("PotionUse", i).equals("1") && (!targetSelf || level > 3))
|
|
err_pr.println("Error: Warning: PotionUse 1 in iprp_spells.2da when spell of level higher than 3rd or not self-targetable on line " + i);
|
|
|
|
if (iprp_spells.getEntry("WandUse", i).equals("0") && level <= 4)
|
|
err_pr.println("Error: Warning: WandUse 0 in iprp_spells.2da when spell of 4th level or less on line " + i);
|
|
if (iprp_spells.getEntry("WandUse", i).equals("1") && level > 4)
|
|
err_pr.println("Error: Warning: WandUse 1 in iprp_spells.2da when spell of level higher than 4th on line " + i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For each actual spell in spells.2da, make sure there is at least one iprp_spells.2da entry
|
|
Set<Integer> spellIDs = new TreeSet<Integer>();
|
|
for (int i = 0; i < spells.getEntryCount(); i++) {
|
|
if (!spells.getEntry("Bard", i).equals("****") ||
|
|
!spells.getEntry("Cleric", i).equals("****") ||
|
|
!spells.getEntry("Druid", i).equals("****") ||
|
|
!spells.getEntry("Paladin", i).equals("****") ||
|
|
!spells.getEntry("Ranger", i).equals("****") ||
|
|
!spells.getEntry("Wiz_Sorc", i).equals("****"))
|
|
spellIDs.add(i);
|
|
}
|
|
for (int i = 0; i < iprp_spells.getEntryCount(); i++) {
|
|
if (!iprp_spells.getEntry("SpellIndex", i).equals("****")) {
|
|
try {
|
|
spellIDs.remove(Integer.parseInt(iprp_spells.getEntry("SpellIndex", i)));
|
|
} catch (NumberFormatException e) {
|
|
err_pr.println("Error: Error: Non-number value in iprp_spells.2da SpellIndex column on line " + i + ": " + iprp_spells.getEntry("SpellIndex", i));
|
|
}
|
|
}
|
|
}
|
|
for (int spellID : spellIDs)
|
|
err_pr.println("Error: Error: Spell " + spellID + " does not have any iprp_spells.2da entries");
|
|
}
|
|
|
|
private static void doSpellsAndDes2dasTest(String twoDAPath) {
|
|
Data_2da spells = twoDA.get("spells"),
|
|
des_crft_scroll = twoDA.get("des_crft_scroll"),
|
|
des_crft_spells = twoDA.get("des_crft_spells");
|
|
|
|
// First, whine about differing lengths on spells and des_crft_spells
|
|
if (spells.getEntryCount() != des_crft_spells.getEntryCount())
|
|
err_pr.println("Error: Warning: spells.2da and des_crft_spells.2da have different number of entries");
|
|
|
|
int maxCommon = Math.min(Math.min(spells.getEntryCount(), des_crft_spells.getEntryCount()), des_crft_scroll.getEntryCount());
|
|
|
|
// First, check labels up to the common max
|
|
for (int i = 0; i < maxCommon; i++) {
|
|
if (!(spells.getEntry("Label", i).equals(des_crft_spells.getEntry("Label", i)) &&
|
|
spells.getEntry("Label", i).equals(des_crft_scroll.getEntry("Label", i)) &&
|
|
des_crft_spells.getEntry("Label", i).equals(des_crft_scroll.getEntry("Label", i))))
|
|
err_pr.println("Error: Warning: Differing Label among spells.2da, des_crft_scroll.2da and des_crft_spells.2da on line: " + i + "('" + spells.getEntry("Label", i) + "','" + des_crft_scroll.getEntry("Label", i) + "','" + des_crft_spells.getEntry("Label", i) + "')");
|
|
}
|
|
|
|
// Then, check spells.2da and des_crft_spells.2da
|
|
int a = 0, b = 0;
|
|
boolean spellsNum, desNum;
|
|
for (int i = 0; i < Math.min(spells.getEntryCount(), des_crft_spells.getEntryCount()); i++) {
|
|
spellsNum = desNum = true;
|
|
// Here, we are only interested in lines that contain something
|
|
if (!(spells.getEntry("Label", i).startsWith("**") ||
|
|
spells.getEntry("Label", i).equals("ReservedForISCAndESS"))
|
|
) {
|
|
if (!spells.getEntry("Label", i).equals(des_crft_spells.getEntry("Label", i)))
|
|
err_pr.println("Error: Warning: Differing Label among spells.2da and des_crft_spells.2da on line: " + i + "('" + spells.getEntry("Label", i) + "','" + des_crft_spells.getEntry("Label", i) + "')");
|
|
try {
|
|
a = Integer.parseInt(spells.getEntry("Innate", i));
|
|
} catch (NumberFormatException e) {
|
|
spellsNum = false;
|
|
}
|
|
try {
|
|
b = Integer.parseInt(des_crft_spells.getEntry("Level", i));
|
|
} catch (NumberFormatException e) {
|
|
desNum = false;
|
|
}
|
|
|
|
// If both are numeric, compare
|
|
if (spellsNum && desNum) {
|
|
if (a != b)
|
|
err_pr.println("Error: Error: Differing Innate and Level values among spells.2da and des_crft_spells.2da on line " + i + ": " + "(" + a + "," + b + ")");
|
|
} // Otherwise, erroneous cases are those where only one value is non-numeric
|
|
else if (!spellsNum && desNum)
|
|
err_pr.println("Error: Error: Non-number value in spells.2da Innate column on line " + i + ": " + spells.getEntry("Innate", i));
|
|
else if (spellsNum && !desNum)
|
|
err_pr.println("Error: Error: Non-number value in des_crft_spells.2da Level column on line " + i + ": " + des_crft_spells.getEntry("Level", i));
|
|
// Or where the non-numericity is not just ****
|
|
else {
|
|
if (!spells.getEntry("Innate", i).equals("****"))
|
|
err_pr.println("Error: Error: Non-number value in spells.2da Innate column on line " + i + ": " + spells.getEntry("Innate", i));
|
|
if (!des_crft_spells.getEntry("Level", i).equals("****"))
|
|
err_pr.println("Error: Error: Non-number value in des_crft_spells.2da Level column on line " + i + ": " + des_crft_spells.getEntry("Level", i));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then check that all spells that have a scroll is are NoScroll 0
|
|
for (int i = 0; i < des_crft_scroll.getEntryCount(); i++) {
|
|
if ((!des_crft_scroll.getEntry("Wiz_Sorc", i).equals("****") ||
|
|
!des_crft_scroll.getEntry("Cleric", i).equals("****") ||
|
|
!des_crft_scroll.getEntry("Paladin", i).equals("****") ||
|
|
!des_crft_scroll.getEntry("Druid", i).equals("****") ||
|
|
!des_crft_scroll.getEntry("Ranger", i).equals("****") ||
|
|
!des_crft_scroll.getEntry("Bard", i).equals("****")
|
|
) &&
|
|
des_crft_spells.getEntry("NoScroll", i).equals("1")
|
|
)
|
|
err_pr.println("Error: Error: NoScroll 1 in des_crft_spells.2da when a scroll entry has been defined in des_crft_scroll.2da on line: " + i);
|
|
}
|
|
|
|
// Then check that all spells that should have a scroll do have a scroll
|
|
for (int i = 0; i < spells.getEntryCount(); i++) {
|
|
checkScrollsPresence(spells, des_crft_scroll, "Bard", i);
|
|
checkScrollsPresence(spells, des_crft_scroll, "Cleric", i);
|
|
checkScrollsPresence(spells, des_crft_scroll, "Druid", i);
|
|
checkScrollsPresence(spells, des_crft_scroll, "Paladin", i);
|
|
checkScrollsPresence(spells, des_crft_scroll, "Ranger", i);
|
|
checkScrollsPresence(spells, des_crft_scroll, "Wiz_Sorc", i);
|
|
}
|
|
}
|
|
|
|
private static void checkScrollsPresence(Data_2da spells, Data_2da des_crft_scroll, String column, int i) {
|
|
if (!spells.getEntry(column, i).equals("****")) {
|
|
if (i >= des_crft_scroll.getEntryCount() || des_crft_scroll.getEntry(column, i).equals("****")) {
|
|
err_pr.println("Error: Error: No " + column + " scroll defined in des_crft_scroll when " + column + " level is defined in spells on line: " + i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 validate 2dadir tlkdir | [--help]\n" +
|
|
"\n" +
|
|
"2dadir Path to a directory containing 2da files\n" +
|
|
"tlkdir Path to a directory containing dialog.tlk and prc8_consortium.tlk\n" +
|
|
"\n" +
|
|
"-p pedantic mode. Makes extra checks\n" +
|
|
"\n" +
|
|
"--help prints this info you are reading\n" +
|
|
"\n" +
|
|
"\n" +
|
|
"Performs a set of validation operations on 2da files.\n"
|
|
);
|
|
System.exit(0);
|
|
}
|
|
}
|