SE3-ArkhamDBExporter/strange_eons_to_arkhamdb.js

284 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

useLibrary('imageutils');
importClass(java.io.File);
importClass(java.io.FileWriter);
importPackage(arkham.project);
// The resolution (in pixels per inch) of the exported images
const RESOLUTION = 300;
// The extension of the image file format to use, e.g., png, jpg
const FORMAT = ImageUtils.FORMAT_PNG;
// TODO: should be defined in strange eons somewhere
const pack_code = "kyo_player";
const cycle_prefix = "43";
function renameSlot(slot) {
if (slot.startsWith('1 ')) {
return slot.slice(2);
}
else if (slot.startsWith('2 ')) {
return slot.slice(2) + ' x2';
}
else {
return slot;
}
}
const tag_replacements = {
"<gua>": "[guardian]",
"<see>": "[seeker]",
"<rog>": "[rogue]",
"<mys>": "[mystic]",
"<sur>": "[survivor]",
"<wil>": "[willpower]",
"<int>": "[intellect]",
"<com>": "[combat]",
"<agi>": "[agility]",
"<wild>": "[wild]",
"<sku>": "[skull]",
"<cul>": "[cultist]",
"<tab>": "[tablet]",
"<mon>": "[elder_thing]",
"<ble>": "[bless]",
"<cur>": "[curse]",
"<eld>": "[eldersign]",
"<ten>": "[auto_fail]",
"<act>": "[action]",
"<fre>": "[free]",
"<rea>": "[reaction]",
"<for>": "<b>Forced</b>",
"<hau>": "<b>Haunted</b>",
"<obj>": "<b>Objective</b>",
"<pat>": "Patrol",
"<rev>": "<b>Revelation</b>",
"<uni>": "{Unique}", // TODO
"<per>": "[per_investigator]",
"<bul>": "- ",
"<squ>": "{Square}", // TODO
// TODO
"<bultab>": "", // Tab spacing for bullet sections
"<t>": "<b><i>", // Trait
"</t>": "</i></b>",
"<hs>": "", // Horizontal spacer
"<lvs>": "", // Large vertical spacer
"<vs>": "", // Vertical spacer
"<svs>": "", // Small vertical spacer
};
// TODO: handle investigator cards
const card_types = {
"AHLCG-Event-Default": "event",
"AHLCG-Skill-Default": "skill",
"AHLCG-Asset-Default": "asset",
// TODO: actually handle enemy weaknesses
"AHLCG-WeaknessEnemy-Default": "enemy",
"AHLCG-WeaknessTreachery-Default": "treachery",
};
function int_or_null(inp) {
if (inp == 'None') {
return null;
}
else if (inp == 'X') {
return -2;
}
else {
return parseInt(inp);
}
}
function leftPad(str, len, fill) {
return fill.repeat(Math.max(len - str.length, 0)) + str;
}
function replaceAll(str, search, replace) {
return str.split(search).join(replace);
}
function javaIteratorToJsGenerator(java_iter) {
while(java_iter.hasNext()) {
yield java_iter.next();
}
}
function recurseAllChildren(parent) {
if (parent == null || !(parent instanceof Member)) {
error('missing parent, or parent is not a Member');
}
for (let child in javaIteratorToJsGenerator(parent.iterator())) {
if (child.isFolder()) {
for (let sub_child in recurseAllChildren(child)) {
yield sub_child;
}
}
else {
yield child;
}
}
}
function build_card(component) {
function substitute_tags(str) {
str = str.trim();
str = replaceAll(str, "<fullname>", String(component.getName()));
for (let tag in tag_replacements) {
str = replaceAll(str, tag, tag_replacements[tag]);
}
return str;
}
const code = cycle_prefix + leftPad(String(component.settings.get('CollectionNumber')), 3, '0');
const card_data = {
code: String(code),
deck_limit: 2, // TODO: could be derived?
flavor: substitute_tags(String(component.settings.get('Flavor'))),
illustrator: String(component.settings.get('Artist')),
is_unique: component.settings.getBoolean('Unique'),
name: substitute_tags(String(component.getName())),
pack_code: pack_code,
position: int_or_null(component.settings.get('CollectionNumber')),
quantity: 2, // TODO
//restrictions: null, // TODO
// TODO: should also handle "Victory" field
text: substitute_tags(String(
component.settings.get('Keywords') + '\n' + component.settings.get('Rules'))),
traits: substitute_tags(String(component.settings.get('Traits'))),
type_code: card_types[component.getFrontTemplateKey()],
xp: int_or_null(component.settings.get('Level')),
};
const raw_health = component.settings.get('Stamina');
if (raw_health && raw_health != 'None' && raw_health != '-') {
card_data.health = int_or_null(raw_health);
}
const raw_sanity = component.settings.get('Sanity');
if (raw_sanity && raw_sanity != 'None' && raw_sanity != '-') {
card_data.sanity = int_or_null(raw_sanity);
}
const skills = {
Agility: 0,
Intellect: 0,
Combat: 0,
Willpower: 0,
Wild: 0,
};
for (let i = 1; i<=6; i++) {
const skill_icon = component.settings.get('Skill' + i);
if (skill_icon in skills) {
skills[skill_icon] += 1;
}
}
for (let skill in skills) {
if (skills[skill] > 0) {
card_data["skill_" + skill.toLowerCase()] = skills[skill];
}
}
const raw_cost = component.settings.get('ResourceCost');
if (raw_cost) {
card_data.cost = int_or_null(raw_cost);
}
const raw_slot = component.settings.get('Slot');
if (raw_slot && raw_slot != 'None') {
card_data.slot = renameSlot(String(raw_slot));
const raw_slot2 = component.settings.get('Slot2');
if (raw_slot2 && raw_slot2 != 'None') {
card_data.slot += '. ' + renameSlot(String(raw_slot2));
}
}
const subtitle = component.settings.get('Subtitle');
if (subtitle && subtitle != '') {
card_data.subname = String(subtitle);
}
const faction = component.settings.get('CardClass');
if (faction) {
if (faction == 'Weakness') {
card_data.subtype_code = "weakness";
}
else if (faction == 'Basic Weakness') {
card_data.subtype_code = "basicweakness";
}
else {
card_data.faction_code = String(faction).toLowerCase();
const faction2 = component.settings.get('CardClass2');
if (faction2 && faction2 != 'None') {
card_data.faction2_code = String(faction2).toLowerCase();
}
}
}
if (card_types[component.getFrontTemplateKey()] == 'enemy') {
// TODO: "weakness" or "basicweakness"
card_data.subtype_code = "basicweakness";
}
// TODO: parse out some keywords into their own fields
// order by keys
const ordered_card_data = Object.keys(card_data).sort().reduce(
function(obj, key) {
obj[key] = card_data[key];
return obj;
},
{}
);
return ordered_card_data;
}
function exportCard(component, file) {
try {
// create the sheets that will paint the faces of the component
let sheets = component.createDefaultSheets();
if (sheets == null) return;
// export front face
let image = sheets[0].paint(arkham.sheet.RenderTarget.EXPORT, RESOLUTION);
ImageUtils.write(image, file, FORMAT, -1, false, RESOLUTION);
} catch (ex) {
println(ex);
println('Error while making image for ' + component.getName() + ', skipping file');
Error.handleUncaught(ex);
}
}
const cards = [];
for (let member in recurseAllChildren(Eons.getOpenProject())) {
try {
if (ProjectUtilities.matchExtension(member, "eon")) {
printf("Generating JSON/PNG for '%s'...\n", member);
let component = ResourceKit.getGameComponentFromFile(member.getFile());
if (component.getFrontTemplateKey() in card_types) {
let card_data = build_card(component);
cards.push(card_data);
const export_dir = new File(member.parent.file, 'export');
export_dir.mkdir();
exportCard(component, new File(export_dir, card_data.code + '.png'));
}
}
} catch (ex) {
println(ex);
println('Error while processing ' + member.name + ', skipping file');
Error.handleUncaught(ex);
}
}
cards.sort(function (a, b) {
return parseInt(a.code) - parseInt(b.code);
});
const file = new File(Eons.getOpenProject().getFile(), pack_code + '.json');
printf("Writing '%s'\n", file);
ProjectUtilities.writeTextFile(file, JSON.stringify(cards, null, 4));
println("Done!");
Eons.getOpenProject().synchronize();