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.
386 lines
18 KiB
Java
386 lines
18 KiB
Java
package prc.utils;
|
|
|
|
import prc.autodoc.*;
|
|
import prc.autodoc.Main.TLKStore;
|
|
import prc.autodoc.Main.TwoDAStore;
|
|
|
|
import java.io.File;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
import java.util.function.Predicate;
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Stream;
|
|
|
|
import static prc.Main.verbose;
|
|
|
|
|
|
/**
|
|
* A little tool that parses des_crft_scroll, extracts unique item resrefs from it and
|
|
* makes a merchant selling those resrefs.
|
|
*
|
|
* @author Heikki 'Ornedan' Aitakangas
|
|
*/
|
|
public class ScrollMerchantGen {
|
|
|
|
/**
|
|
* Ye olde maine methode.
|
|
*
|
|
* @param args The arguments
|
|
* @throws IOException If the writing fails, just die on the exception
|
|
*/
|
|
public static void main(String[] args) throws IOException {
|
|
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) {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load data
|
|
TwoDAStore twoDA = new TwoDAStore(twoDAPath);
|
|
TLKStore tlks = new TLKStore("dialog.tlk", "prc_consortium.tlk", tlkPath);
|
|
|
|
doScrollMerchantGen(twoDA, tlks, "scrolltemp");
|
|
}
|
|
public static Set<Integer> getAllSpellCastingClasses(TwoDAStore twoDA)
|
|
{
|
|
Set<Integer> vanillaSpellcasterClasses = Set.of(1,2,3,6,7,9,10);
|
|
HashSet<Integer> result = new HashSet<Integer>();
|
|
Data_2da classes = twoDA.get("classes");
|
|
for(int i = 0; i < classes.getEntryCount(); i++) {
|
|
// handle the vanilla spellcasters
|
|
if (vanillaSpellcasterClasses.contains(i)) {
|
|
result.add(i);
|
|
continue;
|
|
}
|
|
|
|
boolean isSpellCaster = classes.getEntry("SpellCaster", i).equals("1");
|
|
boolean isPlayerClass = classes.getEntry("PlayerClass", i).equals("1");
|
|
if (!isSpellCaster || !isPlayerClass) continue;
|
|
|
|
// Attempt to load the spellbook 2da for the class. If it fails, not a spellcaster,
|
|
// or a spellcaster that reuses another book (so we don't need a shop for them).
|
|
try {
|
|
String classAbrev = classes.getEntry("FeatsTable", i).toLowerCase().substring(9);
|
|
twoDA.get("cls_spcr_" + classAbrev);
|
|
} catch (TwoDAReadException e) {
|
|
continue;
|
|
}
|
|
|
|
String label = classes.getEntry("Label", i);
|
|
if (!label.equals("****")) result.add(i);
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Performs the scroll merchant generation for all scroll shops.
|
|
* @param twoDA A TwoDAStore for loading 2da data from
|
|
* @param tlks A TLKStore for reading tlk data from
|
|
* @throws IOException Just tossed back up
|
|
*/
|
|
public static void doScrollMerchantGen(TwoDAStore twoDA, TLKStore tlks, String outPath) throws IOException {
|
|
String shopPrefix = "prcSS_";
|
|
List<ScrollInfo> allScrolls = getAllScrolls(twoDA, tlks);
|
|
doScrollMerchantGenShop(twoDA, tlks, outPath, false, allScrolls, "prc_scrolls", scroll -> true);
|
|
for(int classId : getAllSpellCastingClasses(twoDA)) {
|
|
String allClassSpellsShop = shopPrefix + "C" + classId;
|
|
|
|
|
|
doScrollMerchantGenShop(twoDA, tlks, outPath, false, allScrolls, allClassSpellsShop,
|
|
scroll -> scroll.classesForSpellWithLevel.containsKey(classId));
|
|
|
|
for(int level = 0; level <= 9; level++) {
|
|
final int spellLevel = level;
|
|
String shopName = shopPrefix + "C" + classId + "L" + level;
|
|
doScrollMerchantGenShop(twoDA, tlks, outPath, false, allScrolls, shopName,
|
|
scroll -> {
|
|
Map<Integer, Integer> classSpellInfo = scroll.classesForSpellWithLevel;
|
|
return classSpellInfo.containsKey(classId) && classSpellInfo.get(classId).equals(spellLevel);
|
|
});
|
|
}
|
|
}
|
|
char[] letters = "abcdefghijklmnopqrstuvwxyz".toCharArray();
|
|
for(char letter : letters) {
|
|
String shopName = "prcSS_alpha_" + letter;
|
|
doScrollMerchantGenShop(twoDA, tlks, outPath, true, allScrolls, shopName,
|
|
scroll -> scroll.Name.toLowerCase().startsWith("" + letter));
|
|
|
|
}
|
|
}
|
|
|
|
|
|
private static List<ScrollInfo> getAllScrolls(TwoDAStore twoDA, TLKStore tlks)
|
|
{
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
Data_2da scrolls2da = twoDA.get("des_crft_scroll");
|
|
// Loop over the scroll entries and get a list of unique resrefs
|
|
TreeMap<Integer, TreeMap<String, ScrollInfo>> arcaneScrollResRefs = new TreeMap<Integer, TreeMap<String, ScrollInfo>>();
|
|
TreeMap<Integer, TreeMap<String, ScrollInfo>> divineScrollResRefs = new TreeMap<Integer, TreeMap<String, ScrollInfo>>();
|
|
String entry;
|
|
for (int i = 0; i < scrolls2da.getEntryCount(); i++) {
|
|
// Skip subradials
|
|
if (spells2da.getEntry("Master", i).equals("****")) {
|
|
if (!(entry = scrolls2da.getEntry("Wiz_Sorc", i)).equals("****"))
|
|
addScroll(arcaneScrollResRefs, twoDA, tlks, i, entry.toLowerCase());
|
|
if (!(entry = scrolls2da.getEntry("Cleric", i)).equals("****"))
|
|
addScroll(divineScrollResRefs, twoDA, tlks, i, entry.toLowerCase());
|
|
if (!(entry = scrolls2da.getEntry("Paladin", i)).equals("****"))
|
|
addScroll(divineScrollResRefs, twoDA, tlks, i, entry.toLowerCase());
|
|
if (!(entry = scrolls2da.getEntry("Druid", i)).equals("****"))
|
|
addScroll(divineScrollResRefs, twoDA, tlks, i, entry.toLowerCase());
|
|
if (!(entry = scrolls2da.getEntry("Ranger", i)).equals("****"))
|
|
addScroll(divineScrollResRefs, twoDA, tlks, i, entry.toLowerCase());
|
|
if (!(entry = scrolls2da.getEntry("Bard", i)).equals("****"))
|
|
addScroll(arcaneScrollResRefs, twoDA, tlks, i, entry.toLowerCase());
|
|
}
|
|
}
|
|
//int posCounter = 0;
|
|
Stream<ScrollInfo> arcaneLevelScrollRefRefs = arcaneScrollResRefs.values()
|
|
.stream().flatMap(scrollMap -> scrollMap.values().stream());
|
|
Stream<ScrollInfo> divineLevelScrollRefRefs = divineScrollResRefs.values()
|
|
.stream().flatMap(scrollMap -> scrollMap.values().stream());
|
|
Stream<ScrollInfo> allScrollInfos = Stream.concat(arcaneLevelScrollRefRefs, divineLevelScrollRefRefs)
|
|
.distinct()
|
|
.sorted((scroll1, scroll2) -> -scroll1.Name.compareTo(scroll2.Name));
|
|
|
|
return allScrollInfos.collect(Collectors.toList());
|
|
|
|
}
|
|
|
|
/**
|
|
* Performs the scroll merchant generation. Made public for the purposes of BuildScrollHack.
|
|
*
|
|
* @param twoDA A TwoDAStore for loading 2da data from
|
|
* @param tlks A TLKStore for reading tlk data from
|
|
* @param allScrolls A list containing ScrollInfo for all scrolls
|
|
* @param shopName The name of the shop (will generate a <shopName>.utm.xml file)
|
|
* @param scrollFilter A predicate used to filter the scrolls for the given shop
|
|
* @throws IOException Just tossed back up
|
|
*/
|
|
private static void doScrollMerchantGenShop(TwoDAStore twoDA, TLKStore tlks, String outPath,
|
|
boolean generateEmptyShops,
|
|
List<ScrollInfo> allScrolls, String shopName, Predicate<ScrollInfo> scrollFilter) throws IOException {
|
|
// Load the 2da file
|
|
Data_2da scrolls2da = twoDA.get("des_crft_scroll");
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
|
|
|
|
|
|
String xmlPrefix =
|
|
"<gff name=\"" + shopName + ".utm\" type=\"UTM \" version=\"V3.2\" >" + "\n" +
|
|
" <struct id=\"-1\" >" + "\n" +
|
|
" <element name=\"ResRef\" type=\"11\" value=\"" + shopName + "\" />" + "\n" +
|
|
" <element name=\"LocName\" type=\"12\" value=\"64113\" >" + "\n" +
|
|
" <localString languageId=\"0\" value=\"" + shopName + "\" />" + "\n" +
|
|
" </element>" + "\n" +
|
|
" <element name=\"Tag\" type=\"10\" value=\"" + shopName + "\" />" + "\n" +
|
|
" <element name=\"MarkUp\" type=\"5\" value=\"100\" />" + "\n" +
|
|
" <element name=\"MarkDown\" type=\"5\" value=\"100\" />" + "\n" +
|
|
" <element name=\"BlackMarket\" type=\"0\" value=\"0\" />" + "\n" +
|
|
" <element name=\"BM_MarkDown\" type=\"5\" value=\"15\" />" + "\n" +
|
|
" <element name=\"IdentifyPrice\" type=\"5\" value=\"-1\" />" + "\n" +
|
|
" <element name=\"MaxBuyPrice\" type=\"5\" value=\"-1\" />" + "\n" +
|
|
" <element name=\"StoreGold\" type=\"5\" value=\"-1\" />" + "\n" +
|
|
" <element name=\"OnOpenStore\" type=\"11\" value=\"\" />" + "\n" +
|
|
" <element name=\"OnStoreClosed\" type=\"11\" value=\"\" />" + "\n" +
|
|
" <element name=\"WillNotBuy\" type=\"15\" />" + "\n" +
|
|
" <element name=\"WillOnlyBuy\" type=\"15\" >" + "\n" +
|
|
" <struct id=\"97869\" >" + "\n" +
|
|
" <element name=\"BaseItem\" type=\"5\" value=\"29\" />" + "\n" +
|
|
" </struct>" + "\n" +
|
|
" </element>" + "\n" +
|
|
" <element name=\"StoreList\" type=\"15\" >" + "\n" +
|
|
" <struct id=\"0\" />" + "\n" +
|
|
" <struct id=\"4\" />" + "\n" +
|
|
" <struct id=\"2\" >" + "\n" +
|
|
" <element name=\"ItemList\" type=\"15\" >" + "\n";
|
|
String xmlSuffix =
|
|
" </element>" + "\n" +
|
|
" </struct>" + "\n" +
|
|
" <struct id=\"3\" />" + "\n" +
|
|
" <struct id=\"1\" />" + "\n" +
|
|
" </element>" + "\n" +
|
|
" <element name=\"ID\" type=\"0\" value=\"5\" />" + "\n" +
|
|
" <element name=\"Comment\" type=\"10\" value=\"\" />" + "\n" +
|
|
" </struct>" + "\n" +
|
|
"</gff>" + "\n";
|
|
|
|
StringBuffer xmlString = new StringBuffer();
|
|
|
|
Stream<ScrollInfo> allScrollRefRefs = allScrolls.stream().filter(scrollFilter);
|
|
int[] posCounterRef = new int[] { 0 };
|
|
allScrollRefRefs.forEach(scroll -> {
|
|
int posCounter = posCounterRef[0];
|
|
String resref = scroll.ScrollResRef;
|
|
String name = scroll.Name;
|
|
xmlString.append(
|
|
" <struct id=\"" + posCounter + "\" >" + "\n" +
|
|
" <element name=\"InventoryRes\" type=\"11\" value=\"" + resref + "\" />" + "<!-- " + name + " -->" + "\n" +
|
|
" <element name=\"Repos_PosX\" type=\"2\" value=\"" + (posCounter % 10) + "\" />" + "\n" +
|
|
" <element name=\"Repos_Posy\" type=\"2\" value=\"" + (posCounter / 10) + "\" />" + "\n" +
|
|
" <element name=\"Infinite\" type=\"0\" value=\"1\" />" + "\n" +
|
|
" </struct>" + "\n"
|
|
);
|
|
posCounterRef[0]+= 1;
|
|
|
|
});
|
|
if (posCounterRef[0] == 0) {
|
|
System.out.println("No scrolls found for shop " + shopName);
|
|
if (!generateEmptyShops) return;
|
|
}
|
|
|
|
// // Then divine scrolls
|
|
// for (Map<String, String> levelScrollResRefs : divineScrollResRefs.values())
|
|
// for (String name : levelScrollResRefs.keySet()) {
|
|
// String resref = levelScrollResRefs.get(name);
|
|
// xmlString.append(
|
|
// " <struct id=\"" + posCounter + "\" >" + "\n" +
|
|
// " <element name=\"InventoryRes\" type=\"11\" value=\"" + resref + "\" />" + "<!-- " + name + " -->" + "\n" +
|
|
// " <element name=\"Repos_PosX\" type=\"2\" value=\"" + (posCounter % 10) + "\" />" + "\n" +
|
|
// " <element name=\"Repos_Posy\" type=\"2\" value=\"" + (posCounter / 10) + "\" />" + "\n" +
|
|
// " <element name=\"Infinite\" type=\"0\" value=\"1\" />" + "\n" +
|
|
// " </struct>" + "\n"
|
|
// );
|
|
// posCounter++;
|
|
// }
|
|
|
|
String shopFilePath = outPath + File.separator + shopName + ".utm.xml";
|
|
File target = new File(shopFilePath);
|
|
// 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(xmlPrefix + xmlString.toString() + xmlSuffix);
|
|
// Clean up
|
|
writer.flush();
|
|
writer.close();
|
|
}
|
|
|
|
private static class ScrollInfo {
|
|
private TwoDAStore twoDAStore;
|
|
|
|
public ScrollInfo(TwoDAStore twoDA, int rowNum, String scrollResRef, String name) {
|
|
this.twoDAStore = twoDA;
|
|
this.ScrollResRef = scrollResRef;
|
|
this.Name = name;
|
|
|
|
Data_2da spells2da = twoDA.get("spells");
|
|
|
|
|
|
try {
|
|
this.SpellLevel = Integer.parseInt(spells2da.getEntry("Innate", rowNum));
|
|
} catch (NumberFormatException e) {
|
|
System.err.println("Non-number value in spells.2da Innate column on line " + rowNum + ": " + spells2da.getEntry("Innate", rowNum));
|
|
return;
|
|
}
|
|
|
|
Data_2da classes = twoDA.get("classes");
|
|
this.classesForSpellWithLevel = ScrollGen.getClassesForSpell(twoDA, rowNum);
|
|
|
|
}
|
|
public int SpellLevel = -1;
|
|
public String ScrollResRef;
|
|
public String Name;
|
|
public Map<Integer, Integer> classesForSpellWithLevel;
|
|
|
|
public boolean equals(Object other)
|
|
{
|
|
if (this == other) return true;
|
|
if (other == null) return false;
|
|
if (getClass() != other.getClass()) return false;
|
|
ScrollInfo otherScroll = (ScrollInfo)other;
|
|
return ScrollResRef.equals(otherScroll.ScrollResRef);
|
|
}
|
|
public int hashCode() {
|
|
return Objects.hash(ScrollResRef);
|
|
}
|
|
}
|
|
|
|
private static void addScroll(TreeMap<Integer, TreeMap<String, ScrollInfo>> scrollResRefs,
|
|
TwoDAStore twoDAStore, TLKStore tlks, int rowNum, String scrollResRef) {
|
|
int innateLevel = -1,
|
|
tlkRef = -1;
|
|
|
|
Data_2da spells2da = twoDAStore.get("spells");
|
|
// HACK - Skip non-PRC scrolls
|
|
if (!scrollResRef.startsWith("prc_scr_"))
|
|
return;
|
|
|
|
|
|
try {
|
|
tlkRef = Integer.parseInt(spells2da.getEntry("Name", rowNum));
|
|
} catch (NumberFormatException e) {
|
|
System.err.println("Non-number value in spells.2da Name column on line " + rowNum + ": " + spells2da.getEntry("Name", rowNum));
|
|
return;
|
|
}
|
|
ScrollInfo scroll = new ScrollInfo(twoDAStore, rowNum, scrollResRef, tlks.get(tlkRef));
|
|
scroll.SpellLevel = innateLevel;
|
|
|
|
innateLevel = scroll.SpellLevel;
|
|
|
|
if (scrollResRefs.get(innateLevel) == null)
|
|
scrollResRefs.put(innateLevel, new TreeMap<String, ScrollInfo>());
|
|
|
|
scrollResRefs.get(innateLevel).put(tlks.get(tlkRef), scroll);
|
|
}
|
|
|
|
/**
|
|
* 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 scrmrchgen 2dadir tlkdir | [--help]\n" +
|
|
"\n" +
|
|
"2dadir Path to a directory containing des_crft_scroll.2da and spells.2da.\n" +
|
|
"tlkdir Path to a directory containing dialog.tlk and prc_consortium.tlk\n" +
|
|
"\n" +
|
|
"--help prints this info you are reading\n" +
|
|
"\n" +
|
|
"\n" +
|
|
"Generates a merchant file - prc_scrolls.utm - based on the given scroll list\n" +
|
|
"2da file. The merchant file will be written to current directory in Pspeed's\n" +
|
|
"XML <-> Gff -xml format\n"
|
|
);
|
|
System.exit(0);
|
|
}
|
|
}
|
|
|