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 = "42"; 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 = { "": "[guardian]", "": "[seeker]", "": "[rogue]", "": "[mystic]", "": "[survivor]", "": "[willpower]", "": "[intellect]", "": "[combat]", "": "[agility]", "": "[wild]", "": "[skull]", "": "[cultist]", "": "[tablet]", "": "[elder_thing]", "": "[bless]", "": "[curse]", "": "[eldersign]", "": "[auto_fail]", "": "[action]", "": "[free]", "": "[reaction]", "": "Forced", "": "Haunted", "": "Objective", "": "Patrol", "": "Revelation", "": "{Unique}", // TODO "": "[per_investigator]", "": "- ", "": "{Square}", // TODO // TODO "": "", // Tab spacing for bullet sections "": "", // Trait "": "", "": "", // Horizontal spacer "": "", // Large vertical spacer "": "", // Vertical spacer "": "", // 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, "", 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); const file_writer = new FileWriter(file); file_writer.write(JSON.stringify(cards, null, 4)); file_writer.flush(); file_writer.close(); println("Done!"); Eons.getOpenProject().synchronize();