|
|
- /******/ (() => { // webpackBootstrap
- /******/ "use strict";
- var __webpack_exports__ = {};
-
- ;// CONCATENATED MODULE: ./src/constants.js
- const debouncedReload = foundry.utils.debounce(() => window.location.reload(), 100);
-
- const CONSTANTS = {
- MODULE_NAME: "pathmuncher",
- MODULE_FULL_NAME: "Pathmuncher",
- FLAG_NAME: "pathmuncher",
- SETTINGS: {
- // Enable options
- LOG_LEVEL: "log-level",
- RESTRICT_TO_TRUSTED: "restrict-to-trusted",
- ADD_VISION_FEATS: "add-vision-feats",
- },
-
- GET_DEFAULT_SETTINGS() {
- return foundry.utils.deepClone(CONSTANTS.DEFAULT_SETTINGS);
- },
- };
-
- CONSTANTS.DEFAULT_SETTINGS = {
- // Enable options
- [CONSTANTS.SETTINGS.RESTRICT_TO_TRUSTED]: {
- name: `${CONSTANTS.FLAG_NAME}.Settings.RestrictToTrusted.Name`,
- hint: `${CONSTANTS.FLAG_NAME}.Settings.RestrictToTrusted.Hint`,
- scope: "world",
- config: true,
- type: Boolean,
- default: false,
- onChange: debouncedReload,
- },
-
- [CONSTANTS.SETTINGS.ADD_VISION_FEATS]: {
- name: `${CONSTANTS.FLAG_NAME}.Settings.AddVisionFeats.Name`,
- hint: `${CONSTANTS.FLAG_NAME}.Settings.AddVisionFeats.Hint`,
- scope: "player",
- config: true,
- type: Boolean,
- default: true,
- },
-
- // debug
- [CONSTANTS.SETTINGS.LOG_LEVEL]: {
- name: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.Name`,
- hint: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.Hint`,
- scope: "world",
- config: true,
- type: String,
- choices: {
- DEBUG: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.debug`,
- INFO: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.info`,
- WARN: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.warn`,
- ERR: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.error`,
- OFF: `${CONSTANTS.FLAG_NAME}.Settings.LogLevel.off`,
- },
- default: "WARN",
- }
-
- };
-
- CONSTANTS.PATH = `modules/${CONSTANTS.MODULE_NAME}`;
-
- /* harmony default export */ const constants = (CONSTANTS);
-
- ;// CONCATENATED MODULE: ./src/data/equipment.js
- const SWAPS = [
- /^(Greater) (.*)/,
- /^(Lesser) (.*)/,
- /^(Major) (.*)/,
- /^(Moderate) (.*)/,
- /^(Standard) (.*)/,
- ];
-
- // this equipment is named differently in foundry vs pathbuilder
- const EQUIPMENT_RENAME_STATIC_MAP = [
- { pbName: "Chain", foundryName: "Chain (10 feet)" },
- { pbName: "Oil", foundryName: "Oil (1 pint)" },
- { pbName: "Bracelets of Dashing", foundryName: "Bracelet of Dashing" },
- { pbName: "Fingerprinting Kit", foundryName: "Fingerprint Kit" },
- { pbName: "Ladder", foundryName: "Ladder (10-foot)" },
- { pbName: "Mezmerizing Opal", foundryName: "Mesmerizing Opal" },
- { pbName: "Explorer's Clothing", foundryName: "Clothing (Explorer's)" },
- { pbName: "Flaming Star (Greater)", foundryName: "Greater Flaming Star" },
- { pbName: "Potion of Lesser Darkvision", foundryName: "Darkvision Elixir (Lesser)" },
- { pbName: "Potion of Greater Darkvision", foundryName: "Darkvision Elixir (Greater)" },
- { pbName: "Potion of Moderate Darkvision", foundryName: "Darkvision Elixir (Moderate)" },
- { pbName: "Bottled Sunlight", foundryName: "Formulated Sunlight" },
- { pbName: "Magazine (Repeating Hand Crossbow)", foundryName: "Magazine with 5 Bolts" },
- { pbName: "Astrolabe (Standard)", foundryName: "Standard Astrolabe" },
- { pbName: "Skinitch Salve", foundryName: "Skinstitch Salve" },
- { pbName: "Flawless Scale", foundryName: "Abadar's Flawless Scale" },
- { pbName: "Construct Key", foundryName: "Cordelia's Construct Key" },
- { pbName: "Construct Key (Greater)", foundryName: "Cordelia's Greater Construct Key" },
- { pbName: "Lesser Swapping Stone", foundryName: "Lesser Bonmuan Swapping Stone" },
- { pbName: "Major Swapping Stone", foundryName: "Major Bonmuan Swapping Stone" },
- { pbName: "Moderate Swapping Stone", foundryName: "Moderate Bonmuan Swapping Stone" },
- { pbName: "Greater Swapping Stone", foundryName: "Greater Bonmuan Swapping Stone" },
- { pbName: "Heartstone", foundryName: "Skarja's Heartstone" },
- { pbName: "Bullets (10 rounds)", foundryName: "Sling Bullets" },
- ];
-
- function generateDynamicNames(pbName) {
- const result = [];
- // if we have a hardcoded map, don't return here
- for (const reg of SWAPS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: `${match[2]} (${match[1]})`, details: match[2] });
- }
- }
- return result;
- }
-
-
- function EQUIPMENT_RENAME_MAP(pbName = null) {
- const postfixNames = pbName ? generateDynamicNames(pbName) : [];
- return postfixNames.concat(EQUIPMENT_RENAME_STATIC_MAP);
- }
-
-
- // this is equipment is special and shouldn't have the transformations applied to it
- const RESTRICTED_EQUIPMENT = [
- "Bracers of Armor",
- ];
-
- const IGNORED_EQUIPMENT = [
- "Unarmored"
- ];
-
- ;// CONCATENATED MODULE: ./src/utils.js
-
-
- const utils = {
-
- wait: async (ms) => {
- return new Promise((resolve) => {
- setTimeout(resolve, ms);
- });
- },
-
- capitalize: (s) => {
- if (typeof s !== "string") return "";
- return s.charAt(0).toUpperCase() + s.slice(1);
- },
-
- setting: (key) => {
- return game.settings.get(constants.MODULE_NAME, key);
- },
-
- updateSetting: async (key, value) => {
- return game.settings.set(constants.MODULE_NAME, key, value);
- },
-
- getFlags: (actor) => {
- const flags = actor.flags[constants.FLAG_NAME]
- ? actor.flags[constants.FLAG_NAME]
- : {
- pathbuilderId: undefined,
- addFeats: true,
- addEquipment: true,
- addBackground: true,
- addHeritage: true,
- addAncestry: true,
- addSpells: true,
- addMoney: true,
- addTreasure: true,
- addLores: true,
- addWeapons: true,
- addArmor: true,
- addDeity: true,
- addName: true,
- addClass: true,
- addFamiliars: true,
- addFormulas: true,
- askForChoices: false,
- };
- return flags;
- },
-
- setFlags: async (actor, flags) => {
- let updateData = {};
- setProperty(updateData, `flags.${constants.FLAG_NAME}`, flags);
- await actor.update(updateData);
- return actor;
- },
-
- resetFlags: async (actor) => {
- return utils.setFlags(actor, null);
- },
-
-
- getOrCreateFolder: async (root, entityType, folderName, folderColor = "") => {
- let folder = game.folders.contents.find((f) =>
- f.type === entityType && f.name === folderName
- // if a root folder we want to match the root id for the parent folder
- && (root ? root.id : null) === (f.folder?.id ?? null)
- );
- // console.warn(`Looking for ${root} ${entityType} ${folderName}`);
- // console.warn(folder);
- if (folder) return folder;
- folder = await Folder.create(
- {
- name: folderName,
- type: entityType,
- color: folderColor,
- parent: (root) ? root.id : null,
- },
- { displaySheet: false }
- );
- return folder;
- },
-
- // eslint-disable-next-line no-unused-vars
- getFolder: async (kind, subFolder = "", baseFolderName = "Pathmuncher", baseColor = "#6f0006", subColor = "#98020a", typeFolder = true) => {
- let entityTypes = new Map();
- entityTypes.set("pets", "Pets");
-
- const folderName = game.i18n.localize(`${constants.MODULE_NAME}.labels.${kind}`);
- const entityType = entityTypes.get(kind);
- const baseFolder = await utils.getOrCreateFolder(null, entityType, baseFolderName, baseColor);
- const entityFolder = typeFolder ? await utils.getOrCreateFolder(baseFolder, entityType, folderName, subColor) : baseFolder;
- if (subFolder !== "") {
- const subFolderName = subFolder.charAt(0).toUpperCase() + subFolder.slice(1);
- const typeFolder = await utils.getOrCreateFolder(entityFolder, entityType, subFolderName, subColor);
- return typeFolder;
- } else {
- return entityFolder;
- }
- },
-
- };
-
-
- /* harmony default export */ const src_utils = (utils);
-
- ;// CONCATENATED MODULE: ./src/data/features.js
- // these are features which are named differently in pathbuilder to foundry
-
-
-
-
- const POSTFIX_PB_REMOVALS = [
- /(.*) (Racket)$/,
- /(.*) (Style)$/,
- ];
-
- const PREFIX_PB_REMOVALS = [
- /^(Arcane Thesis): (.*)/,
- /^(Arcane School): (.*)/,
- /^(The) (.*)/,
- ];
-
- const PARENTHESIS = [
- /^(.*) \((.*)\)$/,
- ];
-
- const SPLITS = [
- /^(.*): (.*)/,
- ];
-
- const features_SWAPS = [
- /^(Greater) (.*)/,
- /^(Lesser) (.*)/,
- /^(Major) (.*)/,
- ];
-
- const FEAT_RENAME_STATIC_MAP = [
- { pbName: "Aerialist", foundryName: "Shory Aerialist" },
- { pbName: "Aeromancer", foundryName: "Shory Aeromancer" },
- { pbName: "Ancient-Blooded", foundryName: "Ancient-Blooded Dwarf" },
- { pbName: "Antipaladin [Chaotic Evil]", foundryName: "Antipaladin" },
- { pbName: "Ape", foundryName: "Ape Animal Instinct" },
- { pbName: "Aquatic Eyes (Darkvision)", foundryName: "Aquatic Eyes" },
- { pbName: "Astrology", foundryName: "Saoc Astrology" },
- { pbName: "Battle Ready", foundryName: "Battle-Ready Orc" },
- { pbName: "Bite (Gnoll)", foundryName: "Bite" },
- { pbName: "Bloodline: Genie (Efreeti)", foundryName: "Bloodline: Genie" },
- { pbName: "Bloody Debilitations", foundryName: "Bloody Debilitation" },
- { pbName: "Cave Climber Kobold", foundryName: "Caveclimber Kobold" },
- { pbName: "Chosen One", foundryName: "Chosen of Lamashtu" },
- { pbName: "Cognative Mutagen (Greater)", foundryName: "Cognitive Mutagen (Greater)" },
- { pbName: "Cognative Mutagen (Lesser)", foundryName: "Cognitive Mutagen (Lesser)" },
- { pbName: "Cognative Mutagen (Major)", foundryName: "Cognitive Mutagen (Major)" },
- { pbName: "Cognative Mutagen (Moderate)", foundryName: "Cognitive Mutagen (Moderate)" },
- { pbName: "Cognitive Crossover", foundryName: "Kreighton's Cognitive Crossover" },
- { pbName: "Collegiate Attendant Dedication", foundryName: "Magaambyan Attendant Dedication" },
- { pbName: "Construct Carver", foundryName: "Tupilaq Carver" },
- { pbName: "Constructed (Android)", foundryName: "Constructed" },
- { pbName: "Deadly Hair", foundryName: "Syu Tak-nwa's Deadly Hair" },
- { pbName: "Deepvision", foundryName: "Deep Vision" },
- { pbName: "Deflect Arrows", foundryName: "Deflect Arrow" },
- { pbName: "Desecrator [Neutral Evil]", foundryName: "Desecrator" },
- { pbName: "Detective Dedication", foundryName: "Edgewatch Detective Dedication" },
- { pbName: "Duelist Dedication (LO)", foundryName: "Aldori Duelist Dedication" },
- { pbName: "Dwarven Hold Education", foundryName: "Dongun Education" },
- { pbName: "Ember's Eyes (Darkvision)", foundryName: "Ember's Eyes" },
- { pbName: "Enhanced Familiar Feat", foundryName: "Enhanced Familiar" },
- { pbName: "Enigma", foundryName: "Enigma Muse" },
- { pbName: "Escape", foundryName: "Fane's Escape" },
- { pbName: "Eye of the Arcane Lords", foundryName: "Eye of the Arclords" },
- { pbName: "Flip", foundryName: "Farabellus Flip" },
- { pbName: "Fourberie", foundryName: "Fane's Fourberie" },
- { pbName: "Ganzi Gaze (Low-Light Vision)", foundryName: "Ganzi Gaze" },
- { pbName: "Guild Agent Dedication", foundryName: "Pathfinder Agent Dedication" },
- { pbName: "Harmful Font", foundryName: "Divine Font" },
- { pbName: "Healing Font", foundryName: "Divine Font" },
- { pbName: "Heatwave", foundryName: "Heat Wave" },
- { pbName: "Heavenseeker Dedication", foundryName: "Jalmeri Heavenseeker Dedication" },
- { pbName: "Heir of the Astrologers", foundryName: "Heir of the Saoc" },
- { pbName: "High Killer Training", foundryName: "Vernai Training" },
- { pbName: "Ice-Witch", foundryName: "Irriseni Ice-Witch" },
- { pbName: "Impeccable Crafter", foundryName: "Impeccable Crafting" },
- { pbName: "Incredible Beastmaster's Companion", foundryName: "Incredible Beastmaster Companion" },
- { pbName: "Interrogation", foundryName: "Bolera's Interrogation" },
- { pbName: "Katana", foundryName: "Katana Weapon Familiarity" },
- { pbName: "Liberator [Chaotic Good]", foundryName: "Liberator" },
- { pbName: "Lumberjack Dedication", foundryName: "Turpin Rowe Lumberjack Dedication" },
- { pbName: "Maestro", foundryName: "Maestro Muse" },
- { pbName: "Major Lesson I", foundryName: "Major Lesson" },
- { pbName: "Major Lesson II", foundryName: "Major Lesson" },
- { pbName: "Major Lesson III", foundryName: "Major Lesson" },
- { pbName: "Mantis God's Grip", foundryName: "Achaekek's Grip" },
- { pbName: "Marked for Death", foundryName: "Mark for Death" },
- { pbName: "Miraculous Spells", foundryName: "Miraculous Spell" },
- { pbName: "Multifarious", foundryName: "Multifarious Muse" },
- { pbName: "Paladin [Lawful Good]", foundryName: "Paladin" },
- { pbName: "Parry", foundryName: "Aldori Parry" },
- { pbName: "Polymath", foundryName: "Polymath Muse" },
- { pbName: "Precise Debilitation", foundryName: "Precise Debilitations" },
- { pbName: "Quick Climber", foundryName: "Quick Climb" },
- { pbName: "Recognise Threat", foundryName: "Recognize Threat" },
- { pbName: "Redeemer [Neutral Good]", foundryName: "Redeemer" },
- { pbName: "Revivification Protocall", foundryName: "Revivification Protocol" },
- { pbName: "Riposte", foundryName: "Aldori Riposte" },
- { pbName: "Rkoan Arts", foundryName: "Rokoan Arts" },
- { pbName: "Saberteeth", foundryName: "Saber Teeth" },
- { pbName: "Scholarly Recollection", foundryName: "Uzunjati Recollection" },
- { pbName: "Scholarly Storytelling", foundryName: "Uzunjati Storytelling" },
- { pbName: "Secret Lesson", foundryName: "Janatimo's Lessons" },
- { pbName: "Sentry Dedication", foundryName: "Lastwall Sentry Dedication" },
- { pbName: "Stab and Snag", foundryName: "Stella's Stab and Snag" },
- { pbName: "Tenets of Evil", foundryName: "The Tenets of Evil" },
- { pbName: "Tenets of Good", foundryName: "The Tenets of Good" },
- { pbName: "Tongue of the Sun and Moon", foundryName: "Tongue of Sun and Moon" },
- { pbName: "Tribal Bond", foundryName: "Quah Bond" },
- { pbName: "Tyrant [Lawful Evil]", foundryName: "Tyrant" },
- { pbName: "Vestigal Wings", foundryName: "Vestigial Wings" },
- { pbName: "Virtue-Forged Tattooed", foundryName: "Virtue-Forged Tattoos" },
- { pbName: "Wakizashi", foundryName: "Wakizashi Weapon Familiarity" },
- { pbName: "Warden", foundryName: "Lastwall Warden" },
- { pbName: "Warrior", foundryName: "Warrior Muse" },
- { pbName: "Wary Eye", foundryName: "Eye of Ozem" },
- { pbName: "Wayfinder Resonance Infiltrator", foundryName: "Westyr's Wayfinder Repository" },
- { pbName: "Wind God's Fan", foundryName: "Wind God’s Fan" },
- { pbName: "Wind God’s Fan", foundryName: "Wind God's Fan" },
- { pbName: "Black", foundryName: "Black Dragon" },
- { pbName: "Brine", foundryName: "Brine Dragon" },
- { pbName: "Copper", foundryName: "Copper Dragon" },
- { pbName: "Blue", foundryName: "Blue Dragon" },
- { pbName: "Bronze", foundryName: "Bronze Dragon" },
- { pbName: "Cloud", foundryName: "Cloud Dragon" },
- { pbName: "Sky", foundryName: "Sky Dragon" },
- { pbName: "Brass", foundryName: "Brass Dragon" },
- { pbName: "Underworld", foundryName: "Underworld Dragon" },
- { pbName: "Crystal", foundryName: "Crystal Dragon" },
- { pbName: "Forest", foundryName: "Forest Dragon" },
- { pbName: "Green", foundryName: "Green Dragon" },
- { pbName: "Sea", foundryName: "Sea Dragon" },
- { pbName: "Silver", foundryName: "Silver Dragon" },
- { pbName: "White", foundryName: "White Dragon" },
- { pbName: "Sovereign", foundryName: "Sovereign Dragon" },
- { pbName: "Umbral", foundryName: "Umbral Dragon" },
- { pbName: "Red", foundryName: "Red Dragon" },
- { pbName: "Gold", foundryName: "Gold Dragon" },
- { pbName: "Magma", foundryName: "Magma Dragon" },
- ];
-
- function features_generateDynamicNames(pbName) {
- const result = [];
- // if we have a hardcoded map, don't return here
- if (FEAT_RENAME_STATIC_MAP.some((e) => e.pbName === pbName)) return result;
- for (const reg of POSTFIX_PB_REMOVALS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[1], details: match[2] });
- }
- }
- for (const reg of PREFIX_PB_REMOVALS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[2], details: match[1] });
- }
- }
- for (const reg of SPLITS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[2], details: match[1] });
- }
- }
- for (const reg of PARENTHESIS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[1], details: match[2] });
- }
- }
- for (const reg of features_SWAPS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: `${match[2]} (${match[1]})`, details: match[2] });
- }
- }
- return result;
- }
-
- function FEAT_RENAME_MAP(pbName = null) {
- const postfixNames = pbName ? features_generateDynamicNames(pbName) : [];
- return postfixNames.concat(FEAT_RENAME_STATIC_MAP);
- }
-
- const IGNORED_FEATS_LIST = [
- "Unarmored",
- "Simple Weapon Expertise",
- "Spellbook",
- "Energy Emanation", // pathbuilder does not pass through a type for this
- "Imprecise Sense", // this gets picked up and added by granted features
- ];
-
- function IGNORED_FEATS() {
- const visionFeats = src_utils.setting(constants.SETTINGS.ADD_VISION_FEATS) ? [] : ["Low-Light Vision", "Darkvision"];
- return IGNORED_FEATS_LIST.concat(visionFeats);
- }
-
- ;// CONCATENATED MODULE: ./src/logger.js
-
-
-
- const logger = {
- _showMessage: (logLevel, data) => {
- if (!logLevel || !data || typeof logLevel !== "string") {
- return false;
- }
-
- const setting = src_utils.setting(constants.SETTINGS.LOG_LEVEL);
- const logLevels = ["DEBUG", "INFO", "WARN", "ERR", "OFF"];
- const logLevelIndex = logLevels.indexOf(logLevel.toUpperCase());
- if (setting == "OFF" || logLevelIndex === -1 || logLevelIndex < logLevels.indexOf(setting)) {
- return false;
- }
- return true;
- },
- log: (logLevel, ...data) => {
- if (!logger._showMessage(logLevel, data)) {
- return;
- }
-
- logLevel = logLevel.toUpperCase();
-
- let msg = "No logging message provided. Please see the payload for more information.";
- let payload = data.slice();
- if (data[0] && typeof (data[0] == "string")) {
- msg = data[0];
- if (data.length > 1) {
- payload = data.slice(1);
- } else {
- payload = null;
- }
- }
- msg = `${constants.MODULE_NAME} | ${logLevel} > ${msg}`;
- switch (logLevel) {
- case "DEBUG":
- if (payload) {
- console.debug(msg, ...payload); // eslint-disable-line no-console
- } else {
- console.debug(msg); // eslint-disable-line no-console
- }
- break;
- case "INFO":
- if (payload) {
- console.info(msg, ...payload); // eslint-disable-line no-console
- } else {
- console.info(msg); // eslint-disable-line no-console
- }
- break;
- case "WARN":
- if (payload) {
- console.warn(msg, ...payload); // eslint-disable-line no-console
- } else {
- console.warn(msg); // eslint-disable-line no-console
- }
- break;
- case "ERR":
- if (payload) {
- console.error(msg, ...payload); // eslint-disable-line no-console
- } else {
- console.error(msg); // eslint-disable-line no-console
- }
- break;
- default:
- break;
- }
- },
-
- debug: (...data) => {
- logger.log("DEBUG", ...data);
- },
-
- info: (...data) => {
- logger.log("INFO", ...data);
- },
-
- warn: (...data) => {
- logger.log("WARN", ...data);
- },
-
- error: (...data) => {
- logger.log("ERR", ...data);
- },
- };
- /* harmony default export */ const src_logger = (logger);
-
- ;// CONCATENATED MODULE: ./src/app/Pathmuncher.js
- /* eslint-disable no-await-in-loop */
- /* eslint-disable no-continue */
-
-
-
-
-
-
- class Pathmuncher {
-
- // eslint-disable-next-line class-methods-use-this
- EQUIPMENT_RENAME_MAP(name) {
- return EQUIPMENT_RENAME_MAP(name);
- }
-
- getFoundryEquipmentName(pbName) {
- return this.EQUIPMENT_RENAME_MAP(pbName).find((map) => map.pbName == pbName)?.foundryName ?? pbName;
- }
-
- FEAT_RENAME_MAP(name) {
- const dynamicItems = [
- { pbName: "Shining Oath", foundryName: `Shining Oath (${this.getChampionType()})` },
- { pbName: "Counterspell", foundryName: `Counterspell (${src_utils.capitalize(this.getClassSpellCastingType() ?? "")})` },
- { pbName: "Counterspell", foundryName: `Counterspell (${src_utils.capitalize(this.getClassSpellCastingType(true) ?? "")})` },
- { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${this.source.class})` },
- { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${this.source.dualClass})` },
- { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${src_utils.capitalize(this.getClassSpellCastingType() ?? "")} Caster)` },
- { pbName: "Cantrip Expansion", foundryName: `Cantrip Expansion (${src_utils.capitalize(this.getClassSpellCastingType(true) ?? "")} Caster)` },
- ];
- return FEAT_RENAME_MAP(name).concat(dynamicItems);
- }
-
- getFoundryFeatureName(pbName) {
- const match = this.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
- return match ?? { pbName, foundryName: pbName, details: undefined };
- }
-
- // eslint-disable-next-line class-methods-use-this
- get RESTRICTED_EQUIPMENT() {
- return RESTRICTED_EQUIPMENT;
- }
-
- // specials that are handled by Foundry and shouldn't be added
- // eslint-disable-next-line class-methods-use-this
- get IGNORED_FEATURES() {
- return IGNORED_FEATS();
- };
-
- // eslint-disable-next-line class-methods-use-this
- get IGNORED_EQUIPMENT() {
- return IGNORED_EQUIPMENT;
- };
-
- getChampionType() {
- if (this.source.alignment == "LG") return "Paladin";
- else if (this.source.alignment == "CG") return "Liberator";
- else if (this.source.alignment == "NG") return "Redeemer";
- else if (this.source.alignment == "LE") return "Tyrant";
- else if (this.source.alignment == "CE") return "Antipaladin";
- else if (this.source.alignment == "NE") return "Desecrator";
- return "Unknown";
- }
-
- constructor(actor, { addFeats = true, addEquipment = true, addSpells = true, addMoney = true, addLores = true,
- addWeapons = true, addArmor = true, addTreasure = true, addDeity = true, addName = true, addClass = true,
- addBackground = true, addHeritage = true, addAncestry = true, askForChoices = false } = {}
- ) {
- this.actor = actor;
- // note not all these options do anything yet!
- this.options = {
- addTreasure,
- addMoney,
- addFeats,
- addSpells,
- addEquipment,
- addLores,
- addWeapons,
- addArmor,
- addDeity,
- addName,
- addClass,
- addBackground,
- addHeritage,
- addAncestry,
- askForChoices,
- };
- this.source = null;
- this.parsed = {
- specials: [],
- feats: [],
- equipment: [],
- armor: [],
- weapons: [],
- };
- this.usedLocations = new Set();
- this.usedLocationsAlternateRules = new Set();
- this.autoAddedFeatureIds = new Set();
- this.autoAddedFeatureItems = {};
- this.allFeatureRules = {};
- this.autoAddedFeatureRules = {};
- this.grantItemLookUp = {};
- this.autoFeats = [];
- this.result = {
- character: {
- _id: this.actor.id,
- prototypeToken: {},
- },
- class: [],
- deity: [],
- heritage: [],
- ancestry: [],
- background: [],
- casters: [],
- spells: [],
- feats: [],
- weapons: [],
- armor: [],
- equipment: [],
- lores: [],
- money: [],
- treasure: [],
- adventurersPack: {
- item: null,
- contents: [
- { slug: "bedroll", qty: 1 },
- { slug: "chalk", qty: 10 },
- { slug: "flint-and-steel", qty: 1 },
- { slug: "rope", qty: 1 },
- { slug: "rations", qty: 14 },
- { slug: "torch", qty: 5 },
- { slug: "waterskin", qty: 1 },
- ],
- },
- focusPool: 0,
- };
- this.check = {};
- this.bad = [];
- }
-
- async fetchPathbuilder(pathbuilderId) {
- if (!pathbuilderId) {
- const flags = src_utils.getFlags(this.actor);
- pathbuilderId = flags?.pathbuilderId;
- }
- if (pathbuilderId) {
- const jsonData = await foundry.utils.fetchJsonWithTimeout(`https://www.pathbuilder2e.com/json.php?id=${pathbuilderId}`);
- if (jsonData.success) {
- this.source = jsonData.build;
- } else {
- ui.notifications.warn(game.i18n.format(`${constants.FLAG_NAME}.Dialogs.Pathmuncher.FetchFailed`, { pathbuilderId }));
- }
- } else {
- ui.notifications.error(game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.Pathmuncher.NoId`));
- }
- }
-
- getClassAdjustedSpecialNameLowerCase(name) {
- return `${name} (${this.source.class})`.toLowerCase();
- }
-
- getDualClassAdjustedSpecialNameLowerCase(name) {
- return `${name} (${this.source.dualClass})`.toLowerCase();
- }
-
- getAncestryAdjustedSpecialNameLowerCase(name) {
- return `${name} (${this.source.ancestry})`.toLowerCase();
- }
-
- getHeritageAdjustedSpecialNameLowerCase(name) {
- return `${name} (${this.source.heritage})`.toLowerCase();
- }
-
- static getMaterialGrade(material) {
- if (material.toLowerCase().includes("high-grade")) {
- return "high";
- } else if (material.toLowerCase().includes("standard-grade")) {
- return "standard";
- }
- return "low";
- }
-
- static getFoundryFeatLocation(pathbuilderFeatType, pathbuilderFeatLevel) {
- if (pathbuilderFeatType === "Ancestry Feat") {
- return `ancestry-${pathbuilderFeatLevel}`;
- } else if (pathbuilderFeatType === "Class Feat") {
- return `class-${pathbuilderFeatLevel}`;
- } else if (pathbuilderFeatType === "Skill Feat") {
- return `skill-${pathbuilderFeatLevel}`;
- } else if (pathbuilderFeatType === "General Feat") {
- return `general-${pathbuilderFeatLevel}`;
- } else if (pathbuilderFeatType === "Background Feat") {
- return `skill-${pathbuilderFeatLevel}`;
- } else if (pathbuilderFeatType === "Archetype Feat") {
- return `archetype-${pathbuilderFeatLevel}`;
- } else {
- return null;
- }
- }
-
- #generateFoundryFeatLocation(document, feature) {
- if (feature.type && feature.level) {
- const ancestryParagonVariant = game.settings.get("pf2e", "ancestryParagonVariant");
- const dualClassVariant = game.settings.get("pf2e", "dualClassVariant");
- // const freeArchetypeVariant = game.settings.get("pf2e", "freeArchetypeVariant");
- const location = Pathmuncher.getFoundryFeatLocation(feature.type, feature.level);
- if (location && !this.usedLocations.has(location)) {
- document.system.location = location;
- this.usedLocations.add(location);
- } else if (location && this.usedLocations.has(location)) {
- src_logger.debug("Variant feat location", { ancestryParagonVariant, location, feature });
- // eslint-disable-next-line max-depth
- if (ancestryParagonVariant && feature.type === "Ancestry Feat") {
- document.system.location = "ancestry-bonus";
- this.usedLocationsAlternateRules.add(location);
- } else if (dualClassVariant && feature.type === "Class Feat") {
- document.system.location = `dualclass-${feature.level}`;
- this.usedLocationsAlternateRules.add(location);
- }
- }
- }
- }
-
- #processSpecialData(name) {
- if (name.includes("Domain: ")) {
- const domainName = name.split(" ")[1];
- this.parsed.feats.push({ name: "Deity's Domain", extra: domainName });
- return true;
- } else {
- return false;
- }
- }
-
- #nameMap() {
- src_logger.debug("Starting Equipment Rename");
- this.source.equipment
- .filter((e) => e[0] && e[0] !== "undefined")
- .forEach((e) => {
- const name = this.getFoundryEquipmentName(e[0]);
- const item = { pbName: name, qty: e[1], added: false };
- this.parsed.equipment.push(item);
- });
- this.source.armor
- .filter((e) => e && e !== "undefined")
- .forEach((e) => {
- const name = this.getFoundryEquipmentName(e.name);
- const item = mergeObject({ pbName: name, originalName: e.name, added: false }, e);
- this.parsed.armor.push(item);
- });
- this.source.weapons
- .filter((e) => e && e !== "undefined")
- .forEach((e) => {
- const name = this.getFoundryEquipmentName(e.name);
- const item = mergeObject({ pbName: name, originalName: e.name, added: false }, e);
- this.parsed.weapons.push(item);
- });
- src_logger.debug("Finished Equipment Rename");
-
- src_logger.debug("Starting Special Rename");
- this.source.specials
- .filter((special) => special
- && special !== "undefined"
- && special !== "Not Selected"
- && special !== this.source.heritage
- )
- .forEach((special) => {
- const name = this.getFoundryFeatureName(special).foundryName;
- if (!this.#processSpecialData(name) && !this.IGNORED_FEATURES.includes(name)) {
- this.parsed.specials.push({ name, originalName: special, added: false });
- }
- });
- src_logger.debug("Finished Special Rename");
-
- src_logger.debug("Starting Feat Rename");
- this.source.feats
- .filter((feat) => feat[0]
- && feat[0] !== "undefined"
- && feat[0] !== "Not Selected"
- && feat[0] !== this.source.heritage
- )
- .forEach((feat) => {
- const name = this.getFoundryFeatureName(feat[0]).foundryName;
- const data = {
- name,
- extra: feat[1],
- added: false,
- type: feat[2],
- level: feat[3],
- originalName: feat[0],
- };
- this.parsed.feats.push(data);
- });
- src_logger.debug("Finished Feat Rename");
- }
-
- #prepare() {
- this.#nameMap();
- }
-
- static getSizeValue(size) {
- switch (size) {
- case 0:
- return "tiny";
- case 1:
- return "sm";
- case 3:
- return "lg";
- default:
- return "med";
- }
- }
-
- async #processSenses() {
- const senses = [];
- this.source.specials.forEach((special) => {
- if (special === "Low-Light Vision") {
- senses.push({ type: "lowLightVision" });
- } else if (special === "Darkvision") {
- senses.push({ type: "darkvision" });
- } else if (special === "Scent") {
- senses.push({ type: "scent" });
- }
- });
- setProperty(this.result.character, "system.traits.senses", senses);
- }
-
- async #addDualClass(klass) {
- if (!game.settings.get("pf2e", "dualClassVariant")) {
- if (this.source.dualClass && this.source.dualClass !== "") {
- src_logger.warn(`Imported character is dual class but system is not configured for dual class`, {
- class: this.source.class,
- dualClass: this.source.dualClass,
- });
- ui.notifications.warn(`Imported character is dual class but system is not configured for dual class`);
- }
- return;
- }
- if (!this.source.dualClass || this.source.dualClass === "") {
- src_logger.warn(`Imported character not dual class but system is configured for dual class`, {
- class: this.source.class,
- });
- ui.notifications.warn(`Imported character not dual class but system is configured for dual class`);
- return;
- }
-
- // find the dual class
- const compendium = await game.packs.get("pf2e.classes");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
- const foundryName = this.getFoundryFeatureName(this.source.dualClass).foundryName;
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(foundryName))
- ?? index.find((i) => i.system.slug === game.pf2e.system.sluggify(this.source.dualClass));
-
- if (!indexMatch) return;
- const doc = await compendium.getDocument(indexMatch._id);
- const dualClass = doc.toObject();
-
- src_logger.debug(`Dual Class ${dualClass.name} found, squashing things together.`);
-
- klass.name = `${klass.name} - ${dualClass.name}`;
- const ruleEntry = {
- "domain": "all",
- "key": "RollOption",
- "option": `class:${dualClass.system.slug}`
- };
-
- // Attacks
- ["advanced", "martial", "simple", "unarmed"].forEach((key) => {
- if (dualClass.system.attacks[key] > klass.system.attacks[key]) {
- klass.system.attacks[key] = dualClass.system.attacks[key];
- }
- });
- if (klass.system.attacks.martial <= dualClass.system.attacks.other.rank) {
- if (dualClass.system.attacks.other.rank === klass.system.attacks.other.rank) {
- let mashed = `${klass.system.attacks.other.name}, ${dualClass.system.attacks.other.name}`;
- mashed = mashed.replace("and ", "");
- klass.system.attacks.other.name = [...new Set(mashed.split(','))].join(',');
- }
- if (dualClass.system.attacks.other.rank > klass.system.attacks.other.rank) {
- klass.system.attacks.other.name = dualClass.system.attacks.other.name;
- klass.system.attacks.other.rank = dualClass.system.attacks.other.rank;
- }
- }
- if (klass.system.attacks.martial >= dualClass.system.attacks.other.rank
- && klass.system.attacks.martial >= klass.system.attacks.other.rank
- ) {
- klass.system.attacks.other.rank = 0;
- klass.system.attacks.other.name = "";
- }
-
- // Class DC
- if (dualClass.system.classDC > klass.system.classDC) {
- klass.system.classDC = dualClass.system.classDC;
- }
-
- // Defenses
- ["heavy", "light", "medium", "unarmored"].forEach((key) => {
- if (dualClass.system.defenses[key] > klass.system.defenses[key]) {
- klass.system.defenses[key] = dualClass.system.defenses[key];
- }
- });
-
- // Description
- klass.system.description.value = `${klass.system.description.value} ${dualClass.system.description.value}`;
-
- // HP
- if (dualClass.system.hp > klass.system.hp) {
- klass.system.hp = dualClass.system.hp;
- }
-
- // Items
- Object.entries(dualClass.system.items).forEach((i) => {
- if (Object.values(klass.system.items).some((x) => x.uuid === i[1].uuid && x.level > i[1].level)) {
- Object.values(klass.system.items).find((x) => x.uuid === i[1].uuid).level = i[1].level;
- } else if (!Object.values(klass.system.items).some((x) => x.uuid === i[1].uuid && x.level <= i[1].level)) {
- klass.system.items[i[0]] = i[1];
- }
- });
-
- // Key Ability
- dualClass.system.keyAbility.value.forEach((v) => {
- if (!klass.system.keyAbility.value.includes(v)) {
- klass.system.keyAbility.value.push(v);
- }
- });
-
- // Perception
- if (dualClass.system.perception > klass.system.perception) klass.system.perception = dualClass.system.perception;
-
- // Rules
- klass.system.rules.push(ruleEntry);
- dualClass.system.rules.forEach((r) => {
- if (!klass.system.rules.includes(r)) {
- klass.system.rules.push(r);
- }
- });
- klass.system.rules.forEach((r, i) => {
- if (r.path !== undefined) {
- const check = r.path.split('.');
- if (check.includes("data")
- && check.includes("martial")
- && check.includes("rank")
- && klass.system.attacks.martial >= r.value
- ) {
- klass.system.rules.splice(i, 1);
- }
- }
- });
-
- // Saving Throws
- ["fortitude", "reflex", "will"].forEach((key) => {
- if (dualClass.system.savingThrows[key] > klass.system.savingThrows[key]) {
- klass.system.savingThrows[key] = dualClass.system.savingThrows[key];
- }
- });
-
- // Skill Feat Levels
- dualClass.system.skillFeatLevels.value.forEach((v) => {
- klass.system.skillFeatLevels.value.push(v);
- });
- klass.system.skillFeatLevels.value = [...new Set(klass.system.skillFeatLevels.value)].sort((a, b) => {
- return a - b;
- });
-
- // Skill Increase Levels
- dualClass.system.skillIncreaseLevels.value.forEach((v) => {
- klass.system.skillIncreaseLevels.value.push(v);
- });
- klass.system.skillIncreaseLevels.value = [...new Set(klass.system.skillIncreaseLevels.value)].sort((a, b) => {
- return a - b;
- });
-
- // Trained Skills
- if (dualClass.system.trainedSkills.additional > klass.system.trainedSkills.additional) {
- klass.system.trainedSkills.additional = dualClass.system.trainedSkills.additional;
- }
- dualClass.system.trainedSkills.value.forEach((v) => {
- if (!klass.system.trainedSkills.value.includes(v)) {
- klass.system.trainedSkills.value.push(v);
- }
- });
-
- this.result.dualClass = dualClass;
- }
-
- // eslint-disable-next-line class-methods-use-this
- async #processGenericCompendiumLookup(compendiumLabel, name, target) {
- src_logger.debug(`Checking for compendium documents for ${name} (${target}) in ${compendiumLabel}`);
- const compendium = await game.packs.get(compendiumLabel);
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
- const foundryName = this.getFoundryFeatureName(name).foundryName;
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(foundryName))
- ?? index.find((i) => i.system.slug === game.pf2e.system.sluggify(name));
-
- if (indexMatch) {
- const doc = await compendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- if (target === "class") {
- itemData.system.keyAbility.selected = this.source.keyability;
- await this.#addDualClass(itemData);
- }
- itemData._id = foundry.utils.randomID();
- this.#generateGrantItemData(itemData);
- this.result[target].push(itemData);
- await this.#addGrantedItems(itemData);
- return true;
- } else {
- this.bad.push({ pbName: name, type: target, details: { name } });
- return false;
- }
- }
-
- // for grants, e.g. ont he champion "Deity and Cause" where there are choices.
- // how do we determine and match these? should we?
- // "pf2e": {
- // "itemGrants": {
- // "adanye": {
- // "id": "4GHcp3iaREfj2ZgN",
- // "onDelete": "detach"
- // },
- // "paladin": {
- // "id": "HGWkTEatliHgDaEu",
- // "onDelete": "detach"
- // }
- // }
- // }
-
- // "Paladin" (granted by deity and casue)
- // "pf2e": {
- // "grantedBy": {
- // "id": "xnrkrJa2YE1UOAVy",
- // "onDelete": "cascade"
- // },
- // "itemGrants": {
- // "retributiveStrike": {
- // "id": "WVHbj9LljCTovdsv",
- // "onDelete": "detach"
- // }
- // }
- // }
-
- // retributive strike
- // "pf2e": {
- // "grantedBy": {
- // "id": "HGWkTEatliHgDaEu",
- // "onDelete": "cascade"
- // }
-
- #parsedFeatureMatch(type, slug, ignoreAdded) {
- // console.warn(`Trying to find ${slug} in ${type}, ignoreAdded? ${ignoreAdded}`);
- const parsedMatch = this.parsed[type].find((f) =>
- (!ignoreAdded || (ignoreAdded && !f.added))
- && (
- slug === game.pf2e.system.sluggify(f.name)
- || slug === game.pf2e.system.sluggify(this.getClassAdjustedSpecialNameLowerCase(f.name))
- || slug === game.pf2e.system.sluggify(this.getAncestryAdjustedSpecialNameLowerCase(f.name))
- || slug === game.pf2e.system.sluggify(this.getHeritageAdjustedSpecialNameLowerCase(f.name))
- || slug === game.pf2e.system.sluggify(f.originalName)
- || slug === game.pf2e.system.sluggify(this.getClassAdjustedSpecialNameLowerCase(f.originalName))
- || slug === game.pf2e.system.sluggify(this.getAncestryAdjustedSpecialNameLowerCase(f.originalName))
- || slug === game.pf2e.system.sluggify(this.getHeritageAdjustedSpecialNameLowerCase(f.originalName))
- || (game.settings.get("pf2e", "dualClassVariant")
- && (slug === game.pf2e.system.sluggify(this.getDualClassAdjustedSpecialNameLowerCase(f.name))
- || slug === game.pf2e.system.sluggify(this.getDualClassAdjustedSpecialNameLowerCase(f.originalName))
- )
- )
- )
- );
- // console.warn(`Results of find ${slug} in ${type}, ignoreAdded? ${ignoreAdded}`, {
- // slug,
- // parsedMatch,
- // parsed: duplicate(this.parsed),
- // });
- return parsedMatch;
- }
-
- #generatedResultMatch(type, slug) {
- const featMatch = this.result[type].find((f) => slug === f.system.slug);
- return featMatch;
- }
-
- #findAllFeatureMatch(slug, ignoreAdded) {
- const featMatch = this.#parsedFeatureMatch("feats", slug, ignoreAdded);
- if (featMatch) return featMatch;
- const specialMatch = this.#parsedFeatureMatch("specials", slug, ignoreAdded);
- if (specialMatch) return specialMatch;
- const deityMatch = this.#generatedResultMatch("deity", slug);
- return deityMatch;
- // const classMatch = this.#generatedResultMatch("class", slug);
- // return classMatch;
- // const equipmentMatch = this.#generatedResultMatch("equipment", slug);
- // return equipmentMatch;
- }
-
- #createGrantedItem(document, parent) {
- src_logger.debug(`Adding granted item flags to ${document.name} (parent ${parent.name})`);
- const camelCase = game.pf2e.system.sluggify(document.system.slug, { camel: "dromedary" });
- setProperty(parent, `flags.pf2e.itemGrants.${camelCase}`, { id: document._id, onDelete: "detach" });
- setProperty(document, "flags.pf2e.grantedBy", { id: parent._id, onDelete: "cascade" });
- this.autoFeats.push(document);
- if (!this.options.askForChoices) {
- this.result.feats.push(document);
- }
- const featureMatch = this.#findAllFeatureMatch(document.system.slug, true)
- ?? (document.name.includes("(")
- ? this.#findAllFeatureMatch(game.pf2e.system.sluggify(document.name.split("(")[0].trim()), true)
- : undefined
- );
-
- // console.warn(`Matching feature for ${document.name}?`, {
- // featureMatch,
- // });
-
- if (featureMatch) {
- if (hasProperty(featureMatch, "added")) {
- featureMatch.added = true;
- this.#generateFoundryFeatLocation(document, featureMatch);
- }
-
- return;
- }
- if (document.type !== "action") src_logger.warn(`Unable to find parsed feature match for granted feature ${document.name}. This might not be an issue, but might indicate feature duplication.`, { document, parent });
- }
-
- async #featureChoiceMatch(choices, ignoreAdded, adjustName) {
- for (const choice of choices) {
- const doc = adjustName
- ? game.i18n.localize(choice.label)
- : await fromUuid(choice.value);
- if (!doc) continue;
- const slug = adjustName
- ? game.pf2e.system.sluggify(doc)
- : doc.system.slug;
- const featMatch = this.#findAllFeatureMatch(slug, ignoreAdded);
- if (featMatch) {
- if (adjustName && hasProperty(featMatch, "added")) featMatch.added = true;
- src_logger.debug("Choices evaluated", { choices, document, featMatch, choice });
- return choice;
- }
- }
- return undefined;
- }
-
- async #evaluateChoices(document, choiceSet) {
- src_logger.debug(`Evaluating choices for ${document.name}`, { document, choiceSet });
- const tempActor = await this.#generateTempActor();
- const cleansedChoiceSet = deepClone(choiceSet);
- try {
- const item = tempActor.getEmbeddedDocument("Item", document._id);
- const choiceSetRules = new game.pf2e.RuleElements.all.ChoiceSet(cleansedChoiceSet, item);
- const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
- const choices = (await choiceSetRules.inflateChoices()).filter((c) => !c.predicate || c.predicate.test(rollOptions));
-
- src_logger.debug("Starting choice evaluation", {
- document,
- choiceSet,
- item,
- choiceSetRules,
- rollOptions,
- choices,
- });
-
- src_logger.debug("Evaluating choiceset", cleansedChoiceSet);
- const choiceMatch = await this.#featureChoiceMatch(choices, true, cleansedChoiceSet.adjustName);
- src_logger.debug("choiceMatch result", choiceMatch);
- if (choiceMatch) return choiceMatch;
-
- if (typeof cleansedChoiceSet.choices === "string" || Array.isArray(choices)) {
- for (const choice of choices) {
- const featMatch = this.#findAllFeatureMatch(choice.value, true, cleansedChoiceSet.adjustName);
- if (featMatch) {
- src_logger.debug("Choices evaluated", { cleansedChoiceSet, choices, document, featMatch, choice });
- featMatch.added = true;
- choice.nouuid = true;
- return choice;
- }
- }
- }
-
- } catch (err) {
- src_logger.error("Whoa! Something went major bad wrong during choice evaluation", {
- err,
- tempActor: tempActor.toObject(),
- document: duplicate(document),
- choiceSet: duplicate(cleansedChoiceSet),
- });
- throw err;
- } finally {
- await Actor.deleteDocuments([tempActor._id]);
- }
-
- src_logger.debug("Evaluate Choices failed", { choiceSet: cleansedChoiceSet, tempActor, document });
- return undefined;
-
- }
-
- async #resolveInjectedUuid(source, propertyData) {
- if (source === null || typeof source === "number" || (typeof source === "string" && !source.includes("{"))) {
- return source;
- }
-
- // Walk the object tree and resolve any string values found
- if (Array.isArray(source)) {
- for (let i = 0; i < source.length; i++) {
- source[i] = this.#resolveInjectedUuid(source[i]);
- }
- } else if (typeof source === 'object' && source !== null) {
- for (const [key, value] of Object.entries(source)) {
- if (typeof value === "string" || (typeof value === 'object' && value !== null)) {
- source[key] = this.#resolveInjectedUuid(value);
- }
- }
- return source;
- } else if (typeof source === "string") {
- const match = source.match(/{(actor|item|rule)\|(.*?)}/);
- if (match && match[1] === "actor") {
- return String(getProperty(this.result.character, match[1]));
- } else if (match) {
- const value = this.grantItemLookUp[match[0]].uuid;
- if (!value) {
- src_logger.error("Failed to resolve injected property", {
- source,
- propertyData,
- key: match[1],
- prop: match[2],
- });
- }
- return String(value);
- } else {
- src_logger.error("Failed to resolve injected property", {
- source,
- propertyData,
- });
- }
- }
-
- return source;
- }
-
- async #generateGrantItemData(document) {
- src_logger.debug(`Generating grantItem rule lookups for ${document.name}...`, { document: deepClone(document) });
- for (const rule of document.system.rules.filter((r) => r.key === "GrantItem" && r.uuid.includes("{"))) {
- src_logger.debug("Generating rule for...", { document: deepClone(document), rule });
- const match = rule.uuid.match(/{(item|rule)\|(.*?)}/);
- if (match) {
- const flagName = match[2].split(".").pop();
- const choiceSet = document.system.rules.find((rule) => rule.key === "ChoiceSet" && rule.flag === flagName)
- ?? document.system.rules.find((rule) => rule.key === "ChoiceSet");
- const choice = choiceSet ? (await this.#evaluateChoices(document, choiceSet)) : undefined;
- const value = choice?.value ?? undefined;
- if (!value) {
- src_logger.warn("Failed to resolve injected uuid", {
- ruleData: choiceSet,
- flagName,
- key: match[1],
- prop: match[2],
- value,
- });
- } else {
- src_logger.debug(`Generated lookup ${value} for key ${document.name}`);
- }
- this.grantItemLookUp[rule.uuid] = {
- docId: document.id,
- key: rule.uuid,
- choice,
- uuid: value,
- flag: flagName,
- choiceSet,
- };
- this.grantItemLookUp[`${document._id}-${flagName}`] = {
- docId: document.id,
- key: rule.uuid,
- choice,
- uuid: value,
- flag: flagName,
- choiceSet,
- };
- this.grantItemLookUp[`${document._id}`] = {
- docId: document.id,
- key: rule.uuid,
- choice,
- uuid: value,
- flag: flagName,
- choiceSet,
- };
- this.grantItemLookUp[`${document._id}-${flagName}`] = {
- docId: document.id,
- key: rule.uuid,
- choice,
- uuid: value,
- flag: flagName,
- choiceSet,
- };
- } else {
- src_logger.error("Failed to resolve injected uuid", {
- document,
- rule,
- });
- }
- }
- }
-
- async #checkRule(document, rule) {
- const tempActor = await this.#generateTempActor([document]);
- const cleansedRule = deepClone(rule);
- try {
- const item = tempActor.getEmbeddedDocument("Item", document._id);
- const ruleElement = cleansedRule.key === "ChoiceSet"
- ? new game.pf2e.RuleElements.all.ChoiceSet(cleansedRule, item)
- : new game.pf2e.RuleElements.all.GrantItem(cleansedRule, item);
- const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
- const choices = cleansedRule.key === "ChoiceSet"
- ? (await ruleElement.inflateChoices()).filter((c) => !c.predicate || c.predicate.test(rollOptions))
- : [ruleElement.resolveValue()];
-
- const isGood = cleansedRule.key === "ChoiceSet"
- ? (await this.#featureChoiceMatch(choices, false)) !== undefined
- : ruleElement.test(rollOptions);
-
- return isGood;
- } catch (err) {
- src_logger.error("Something has gone most wrong during rule checking", {
- document,
- rule: cleansedRule,
- tempActor,
- });
- throw err;
- } finally {
- await Actor.deleteDocuments([tempActor._id]);
- }
- }
-
- // eslint-disable-next-line complexity
- async #addGrantedRules(document) {
- if (document.system.rules.length === 0) return;
- src_logger.debug(`addGrantedRules for ${document.name}`, duplicate(document));
-
- if (hasProperty(document, "system.level.value")
- && document.system.level.value > this.result.character.system.details.level.value
- ) {
- return;
- }
-
- // const rulesToKeep = document.system.rules.filter((r) => !["GrantItem", "ChoiceSet", "MartialProficiency"].includes(r.key));
- const rulesToKeep = [];
- this.allFeatureRules[document._id] = deepClone(document.system.rules);
- this.autoAddedFeatureRules[document._id] = deepClone(document.system.rules.filter((r) => !["GrantItem", "ChoiceSet"].includes(r.key)));
- await this.#generateGrantItemData(document);
-
- const grantRules = document.system.rules.filter((r) => r.key === "GrantItem");
- const choiceRules = document.system.rules.filter((r) => r.key === "ChoiceSet");
-
- for (const ruleTypes of [choiceRules, grantRules]) {
- for (const rawRuleEntry of ruleTypes) {
- const ruleEntry = deepClone(rawRuleEntry);
- src_logger.debug(`Checking ${document.name} rule key: ${ruleEntry.key}`);
-
- const lookupName = ruleEntry.flag ? `${document._id}-${ruleEntry.flag}` : document._id;
-
- src_logger.debug("Rule check, looking up", {
- id: `${document._id}-${ruleEntry.flag}`,
- lookup: this.grantItemLookUp[lookupName],
- lookups: this.grantItemLookUp,
- ruleEntry,
- lookupName,
- });
-
- // have we pre-evaluated this choice?
- const choice = ruleEntry.key === "ChoiceSet"
- ? this.grantItemLookUp[lookupName]?.choice
- ? this.grantItemLookUp[lookupName].choice
- : await this.#evaluateChoices(document, ruleEntry)
- : undefined;
-
- const uuid = ruleEntry.key === "GrantItem"
- ? await this.#resolveInjectedUuid(ruleEntry.uuid, ruleEntry)
- : choice?.value;
-
- src_logger.debug(`UUID for ${document.name}: "${uuid}"`, document, ruleEntry, choice);
- const ruleFeature = uuid ? await fromUuid(uuid) : undefined;
- if (ruleFeature) {
- const featureDoc = ruleFeature.toObject();
- featureDoc._id = foundry.utils.randomID();
- if (featureDoc.system.rules) this.allFeatureRules[document._id] = deepClone(document.system.rules);
- setProperty(featureDoc, "flags.pathmuncher.origin.uuid", uuid);
- if (this.autoAddedFeatureIds.has(`${ruleFeature.id}${ruleFeature.type}`)) {
- src_logger.debug(`Feature ${featureDoc.name} found for ${document.name}, but has already been added (${ruleFeature.id})`, ruleFeature);
- continue;
- }
- src_logger.debug(`Found rule feature ${featureDoc.name} for ${document.name} for`, ruleEntry);
-
- if (ruleEntry.predicate) {
- const testResult = await this.#checkRule(featureDoc, ruleEntry);
- // eslint-disable-next-line max-depth
- if (!testResult) {
- const data = { document, ruleEntry, featureDoc, testResult };
- src_logger.debug(`The test failed for ${document.name} rule key: ${ruleFeature.key} (This is probably not a problem).`, data);
- continue;
- }
- }
- if (choice) {
- ruleEntry.selection = choice.value;
- }
- this.autoAddedFeatureIds.add(`${ruleFeature.id}${ruleFeature.type}`);
- featureDoc._id = foundry.utils.randomID();
- this.#createGrantedItem(featureDoc, document);
- if (hasProperty(ruleFeature, "system.rules.length")) await this.#addGrantedRules(featureDoc);
- } else if (choice?.nouuid) {
- src_logger.debug("Parsed no id rule", { choice, uuid, ruleEntry });
- if (!ruleEntry.flag) ruleEntry.flag = game.pf2e.system.sluggify(document.name, { camel: "dromedary" });
- ruleEntry.selection = choice.value;
- if (choice.label) document.name = `${document.name} (${choice.label})`;
- } else if (choice && uuid && !hasProperty(ruleEntry, "selection")) {
- src_logger.debug("Parsed odd choice rule", { choice, uuid, ruleEntry });
- if (!ruleEntry.flag) ruleEntry.flag = game.pf2e.system.sluggify(document.name, { camel: "dromedary" });
- ruleEntry.selection = choice.value;
- if (ruleEntry.adjustName && choice.label) {
- const label = game.i18n.localize(choice.label);
- const name = `${document.name} (${label})`;
- const pattern = (() => {
- const escaped = RegExp.escape(label);
- return new RegExp(`\\(${escaped}\\) \\(${escaped}\\)$`);
- })();
- document.name = name.replace(pattern, `(${label})`);
- }
- } else {
- const data = {
- uuid: ruleEntry.uuid,
- document,
- ruleEntry,
- choice,
- lookup: this.grantItemLookUp[ruleEntry.uuid],
- };
- if (ruleEntry.key === "GrantItem" && this.grantItemLookUp[ruleEntry.uuid]) {
- rulesToKeep.push(ruleEntry);
- // const lookup = this.grantItemLookUp[ruleEntry.uuid].choiceSet
- // eslint-disable-next-line max-depth
- // if (!rulesToKeep.some((r) => r.key == lookup && r.prompt === lookup.prompt)) {
- // rulesToKeep.push(this.grantItemLookUp[ruleEntry.uuid].choiceSet);
- // }
- } else if (ruleEntry.key === "ChoiceSet" && !hasProperty(ruleEntry, "flag")) {
- src_logger.debug("Prompting user for choices", ruleEntry);
- rulesToKeep.push(ruleEntry);
- }
- src_logger.warn("Unable to determine granted rule feature, needs better parser", data);
- }
- this.autoAddedFeatureRules[document._id].push(ruleEntry);
- }
- }
- if (!this.options.askForChoices) {
- // eslint-disable-next-line require-atomic-updates
- document.system.rules = rulesToKeep;
- }
- }
-
- async #addGrantedItems(document) {
- if (hasProperty(document, "system.items")) {
- src_logger.debug(`addGrantedItems for ${document.name}`, duplicate(document));
- if (!this.autoAddedFeatureItems[document._id]) {
- this.autoAddedFeatureItems[document._id] = duplicate(document.system.items);
- }
- const failedFeatureItems = {};
- for (const [key, grantedItemFeature] of Object.entries(document.system.items)) {
- src_logger.debug(`Checking granted item ${document.name}, with key: ${key}`, grantedItemFeature);
- if (grantedItemFeature.level > getProperty(this.result.character, "system.details.level.value")) continue;
- const feature = await fromUuid(grantedItemFeature.uuid);
- if (!feature) {
- const data = { uuid: grantedItemFeature.uuid, grantedFeature: grantedItemFeature, feature };
- src_logger.warn("Unable to determine granted item feature, needs better parser", data);
- failedFeatureItems[key] = grantedItemFeature;
- continue;
- }
- this.autoAddedFeatureIds.add(`${feature.id}${feature.type}`);
- const featureDoc = feature.toObject();
- featureDoc._id = foundry.utils.randomID();
- setProperty(featureDoc.system, "location", document._id);
- this.#createGrantedItem(featureDoc, document);
- if (hasProperty(featureDoc, "system.rules")) await this.#addGrantedRules(featureDoc);
- }
- if (!this.options.askForChoices) {
- // eslint-disable-next-line require-atomic-updates
- document.system.items = failedFeatureItems;
- }
- }
- if (hasProperty(document, "system.rules")) await this.#addGrantedRules(document);
-
- }
-
- async #detectGrantedFeatures() {
- if (this.result.class.length > 0) await this.#addGrantedItems(this.result.class[0]);
- if (this.result.ancestry.length > 0) await this.#addGrantedItems(this.result.ancestry[0]);
- if (this.result.heritage.length > 0) await this.#addGrantedItems(this.result.heritage[0]);
- if (this.result.background.length > 0) await this.#addGrantedItems(this.result.background[0]);
- }
-
- async #processCore() {
- setProperty(this.result.character, "name", this.source.name);
- setProperty(this.result.character, "prototypeToken.name", this.source.name);
- setProperty(this.result.character, "system.details.level.value", this.source.level);
- if (this.source.age !== "Not set") setProperty(this.result.character, "system.details.age.value", this.source.age);
- if (this.source.gender !== "Not set") setProperty(this.result.character, "system.details.gender.value", this.source.gender);
- setProperty(this.result.character, "system.details.alignment.value", this.source.alignment);
- setProperty(this.result.character, "system.details.keyability.value", this.source.keyability);
- if (this.source.deity !== "Not set") setProperty(this.result.character, "system.details.deity.value", this.source.deity);
- setProperty(this.result.character, "system.traits.size.value", Pathmuncher.getSizeValue(this.source.size));
- setProperty(this.result.character, "system.traits.languages.value", this.source.languages.map((l) => l.toLowerCase()));
-
- this.#processSenses();
-
- setProperty(this.result.character, "system.abilities.str.value", this.source.abilities.str);
- setProperty(this.result.character, "system.abilities.dex.value", this.source.abilities.dex);
- setProperty(this.result.character, "system.abilities.con.value", this.source.abilities.con);
- setProperty(this.result.character, "system.abilities.int.value", this.source.abilities.int);
- setProperty(this.result.character, "system.abilities.wis.value", this.source.abilities.wis);
- setProperty(this.result.character, "system.abilities.cha.value", this.source.abilities.cha);
-
- setProperty(this.result.character, "system.saves.fortitude.tank", this.source.proficiencies.fortitude / 2);
- setProperty(this.result.character, "system.saves.reflex.value", this.source.proficiencies.reflex / 2);
- setProperty(this.result.character, "system.saves.will.value", this.source.proficiencies.will / 2);
-
- setProperty(this.result.character, "system.martial.advanced.rank", this.source.proficiencies.advanced / 2);
- setProperty(this.result.character, "system.martial.heavy.rank", this.source.proficiencies.heavy / 2);
- setProperty(this.result.character, "system.martial.light.rank", this.source.proficiencies.light / 2);
- setProperty(this.result.character, "system.martial.medium.rank", this.source.proficiencies.medium / 2);
- setProperty(this.result.character, "system.martial.unarmored.rank", this.source.proficiencies.unarmored / 2);
- setProperty(this.result.character, "system.martial.martial.rank", this.source.proficiencies.martial / 2);
- setProperty(this.result.character, "system.martial.simple.rank", this.source.proficiencies.simple / 2);
- setProperty(this.result.character, "system.martial.unarmed.rank", this.source.proficiencies.unarmed / 2);
-
- setProperty(this.result.character, "system.skills.acr.rank", this.source.proficiencies.acrobatics / 2);
- setProperty(this.result.character, "system.skills.arc.rank", this.source.proficiencies.arcana / 2);
- setProperty(this.result.character, "system.skills.ath.rank", this.source.proficiencies.athletics / 2);
- setProperty(this.result.character, "system.skills.cra.rank", this.source.proficiencies.crafting / 2);
- setProperty(this.result.character, "system.skills.dec.rank", this.source.proficiencies.deception / 2);
- setProperty(this.result.character, "system.skills.dip.rank", this.source.proficiencies.diplomacy / 2);
- setProperty(this.result.character, "system.skills.itm.rank", this.source.proficiencies.intimidation / 2);
- setProperty(this.result.character, "system.skills.med.rank", this.source.proficiencies.medicine / 2);
- setProperty(this.result.character, "system.skills.nat.rank", this.source.proficiencies.nature / 2);
- setProperty(this.result.character, "system.skills.occ.rank", this.source.proficiencies.occultism / 2);
- setProperty(this.result.character, "system.skills.prf.rank", this.source.proficiencies.performance / 2);
- setProperty(this.result.character, "system.skills.rel.rank", this.source.proficiencies.religion / 2);
- setProperty(this.result.character, "system.skills.soc.rank", this.source.proficiencies.society / 2);
- setProperty(this.result.character, "system.skills.ste.rank", this.source.proficiencies.stealth / 2);
- setProperty(this.result.character, "system.skills.sur.rank", this.source.proficiencies.survival / 2);
- setProperty(this.result.character, "system.skills.thi.rank", this.source.proficiencies.thievery / 2);
-
- setProperty(this.result.character, "system.attributes.perception.rank", this.source.proficiencies.perception / 2);
- setProperty(this.result.character, "system.attributes.classDC.rank", this.source.proficiencies.classDC / 2);
- }
-
- #indexFind(index, arrayOfNameMatches) {
- for (const name of arrayOfNameMatches) {
- const indexMatch = index.find((i) =>
- i.system.slug === game.pf2e.system.sluggify(name)
- || i.system.slug === game.pf2e.system.sluggify(this.getClassAdjustedSpecialNameLowerCase(name))
- || i.system.slug === game.pf2e.system.sluggify(this.getAncestryAdjustedSpecialNameLowerCase(name))
- || i.system.slug === game.pf2e.system.sluggify(this.getHeritageAdjustedSpecialNameLowerCase(name))
- || (game.settings.get("pf2e", "dualClassVariant")
- && (i.system.slug === game.pf2e.system.sluggify(this.getDualClassAdjustedSpecialNameLowerCase(name))
- )
- )
- );
- if (indexMatch) return indexMatch;
- }
- return undefined;
- }
-
- async #generateFeatItems(compendiumLabel) {
- const compendium = await game.packs.get(compendiumLabel);
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
-
- this.parsed.feats.sort((f1, f2) => {
- const f1RefUndefined = !(typeof f1.type === "string" || f1.type instanceof String);
- const f2RefUndefined = !(typeof f2.type === "string" || f2.type instanceof String);
- if (f1RefUndefined || f2RefUndefined) {
- if (f1RefUndefined && f2RefUndefined) {
- return 0;
- } else if (f1RefUndefined) {
- return 1;
- } else {
- return -1;
- }
- }
- return 0;
- });
- for (const featArray of [this.parsed.feats, this.parsed.specials]) {
- for (const pBFeat of featArray) {
- if (pBFeat.added) continue;
- src_logger.debug("Generating feature for", pBFeat);
-
- const indexMatch = this.#indexFind(index, [pBFeat.name, pBFeat.originalName]);
- const displayName = pBFeat.extra ? `${pBFeat.name} (${pBFeat.extra})` : pBFeat.name;
- if (!indexMatch) {
- src_logger.debug(`Unable to match feat ${displayName}`, { displayName, name: pBFeat.name, extra: pBFeat.extra, pBFeat, compendiumLabel });
- this.check[pBFeat.originalName] = { name: displayName, type: "feat", details: { displayName, name: pBFeat.name, originalName: pBFeat.originalName, extra: pBFeat.extra, pBFeat, compendiumLabel } };
- continue;
- }
- if (this.check[pBFeat.originalName]) delete this.check[pBFeat.originalName];
- pBFeat.added = true;
- if (this.autoAddedFeatureIds.has(`${indexMatch._id}${indexMatch.type}`)) {
- src_logger.debug("Feat included in class features auto add", { displayName, pBFeat, compendiumLabel });
- continue;
- }
-
- const doc = await compendium.getDocument(indexMatch._id);
- const item = doc.toObject();
- item._id = foundry.utils.randomID();
- item.name = displayName;
-
- this.#generateFoundryFeatLocation(item, pBFeat);
- this.result.feats.push(item);
- await this.#addGrantedItems(item);
- }
- }
- }
-
- async #generateSpecialItems(compendiumLabel) {
- const compendium = await game.packs.get(compendiumLabel);
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
-
- for (const special of this.parsed.specials) {
- if (special.added) continue;
- src_logger.debug("Generating special for", special);
- const indexMatch = this.#indexFind(index, [special.name, special.originalName]);
- if (!indexMatch) {
- src_logger.debug(`Unable to match special ${special.name}`, { special: special.name, compendiumLabel });
- this.check[special.originalName] = { name: special.name, type: "special", details: { displayName: special.name, name: special.name, originalName: special.originalName, special } };
- continue;
- }
- special.added = true;
- if (this.check[special.originalName]) delete this.check[special.originalName];
- if (this.autoAddedFeatureIds.has(`${indexMatch._id}${indexMatch.type}`)) {
- src_logger.debug("Special included in class features auto add", { special: special.name, compendiumLabel });
- continue;
- }
-
- const doc = await compendium.getDocument(indexMatch._id);
- const docData = doc.toObject();
- docData._id = foundry.utils.randomID();
- this.result.feats.push(docData);
- await this.#addGrantedItems(docData);
- }
- }
-
- async #generateEquipmentItems(pack = "pf2e.equipment-srd") {
- const compendium = game.packs.get(pack);
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
- const compendiumBackpack = await compendium.getDocument("3lgwjrFEsQVKzhh7");
-
- const adventurersPack = this.parsed.equipment.find((e) => e.pbName === "Adventurer's Pack");
- const backpackInstance = adventurersPack ? compendiumBackpack.toObject() : null;
- if (backpackInstance) {
- adventurersPack.added = true;
- backpackInstance._id = foundry.utils.randomID();
- this.result.adventurersPack.item = adventurersPack;
- this.result.equipment.push(backpackInstance);
- for (const content of this.result.adventurersPack.contents) {
- const indexMatch = index.find((i) => i.system.slug === content.slug);
- if (!indexMatch) {
- src_logger.error(`Unable to match adventurers kit item ${content.name}`, content);
- continue;
- }
-
- const doc = await compendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.quantity = content.qty;
- itemData.system.containerId = backpackInstance?._id;
- this.result.equipment.push(itemData);
- }
- }
-
- for (const e of this.parsed.equipment) {
- if (e.pbName === "Adventurer's Pack") continue;
- if (e.added) continue;
- if (this.IGNORED_EQUIPMENT.includes(e.pbName)) {
- e.added = true;
- continue;
- }
- src_logger.debug("Generating item for", e);
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(e.pbName));
- if (!indexMatch) {
- src_logger.error(`Unable to match ${e.pbName}`, e);
- this.bad.push({ pbName: e.pbName, type: "equipment", details: { e } });
- continue;
- }
-
- const doc = await compendium.getDocument(indexMatch._id);
- if (doc.type != "kit") {
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.quantity = e.qty;
- const type = doc.type === "treasure" ? "treasure" : "equipment";
- this.result[type].push(itemData);
- }
- // eslint-disable-next-line require-atomic-updates
- e.added = true;
- }
- }
-
- async #generateWeaponItems() {
- const compendium = game.packs.get("pf2e.equipment-srd");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
-
- for (const w of this.parsed.weapons) {
- if (this.IGNORED_EQUIPMENT.includes(w.pbName)) {
- w.added = true;
- continue;
- }
- src_logger.debug("Generating weapon for", w);
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(w.pbName));
- if (!indexMatch) {
- src_logger.error(`Unable to match weapon item ${w.name}`, w);
- this.bad.push({ pbName: w.pbName, type: "weapon", details: { w } });
- continue;
- }
-
- const doc = await compendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.quantity = w.qty;
- itemData.system.damage.die = w.die;
- itemData.system.potencyRune.value = w.pot;
- itemData.system.strikingRune.value = w.str;
-
- if (w.runes[0]) itemData.system.propertyRune1.value = game.pf2e.system.sluggify(w.runes[0], { camel: "dromedary" });
- if (w.runes[1]) itemData.system.propertyRune2.value = game.pf2e.system.sluggify(w.runes[1], { camel: "dromedary" });
- if (w.runes[2]) itemData.system.propertyRune3.value = game.pf2e.system.sluggify(w.runes[2], { camel: "dromedary" });
- if (w.runes[3]) itemData.system.propertyRune4.value = game.pf2e.system.sluggify(w.runes[3], { camel: "dromedary" });
- if (w.mat) {
- const material = w.mat.split(" (")[0];
- itemData.system.preciousMaterial.value = game.pf2e.system.sluggify(material, { camel: "dromedary" });
- itemData.system.preciousMaterialGrade.value = Pathmuncher.getMaterialGrade(w.mat);
- }
- if (w.display) itemData.name = w.display;
-
- this.result.weapons.push(itemData);
- w.added = true;
- }
- }
-
- async #generateArmorItems() {
- const compendium = game.packs.get("pf2e.equipment-srd");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
-
- for (const a of this.parsed.armor) {
- if (this.IGNORED_EQUIPMENT.includes(a.pbName)) {
- a.added = true;
- continue;
- }
- src_logger.debug("Generating armor for", a);
- const indexMatch = index.find((i) =>
- i.system.slug === game.pf2e.system.sluggify(a.pbName)
- || i.system.slug === game.pf2e.system.sluggify(`${a.pbName} Armor`)
- );
- if (!indexMatch) {
- src_logger.error(`Unable to match armor kit item ${a.name}`, a);
- this.bad.push({ pbName: a.pbName, type: "armor", details: { a } });
- continue;
- }
-
- const doc = await compendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.equipped.value = a.worn ?? false;
- if (!this.RESTRICTED_EQUIPMENT.some((i) => itemData.name.startsWith(i))) {
- itemData.system.equipped.inSlot = a.worn ?? false;
- itemData.system.quantity = a.qty;
- itemData.system.category = a.prof;
- itemData.system.potencyRune.value = a.pot;
- itemData.system.resiliencyRune.value = a.res;
-
- const isShield = itemData.system.category === "shield";
- itemData.system.equipped.handsHeld = isShield && a.worn ? 1 : 0;
- itemData.system.equipped.carryType = isShield && a.worn ? "held" : "worn";
-
- if (a.runes[0]) itemData.system.propertyRune1.value = game.pf2e.system.sluggify(a.runes[0], { camel: "dromedary" });
- if (a.runes[1]) itemData.system.propertyRune2.value = game.pf2e.system.sluggify(a.runes[1], { camel: "dromedary" });
- if (a.runes[2]) itemData.system.propertyRune3.value = game.pf2e.system.sluggify(a.runes[2], { camel: "dromedary" });
- if (a.runes[3]) itemData.system.propertyRune4.value = game.pf2e.system.sluggify(a.runes[3], { camel: "dromedary" });
- if (a.mat) {
- const material = a.mat.split(" (")[0];
- itemData.system.preciousMaterial.value = game.pf2e.system.sluggify(material, { camel: "dromedary" });
- itemData.system.preciousMaterialGrade.value = Pathmuncher.getMaterialGrade(a.mat);
- }
- }
- if (a.display) itemData.name = a.display;
-
- this.result.armor.push(itemData);
- // eslint-disable-next-line require-atomic-updates
- a.added = true;
- }
- }
-
- getClassSpellCastingType(dual = false) {
- const classCaster = dual
- ? this.source.spellCasters.find((caster) => caster.name === this.source.dualClass)
- : this.source.spellCasters.find((caster) => caster.name === this.source.class);
- const type = classCaster?.spellcastingType;
- if (type || this.source.spellCasters.length === 0) return type ?? "spontaneous";
- // if no type and multiple spell casters, then return the first spell casting type
- return this.source.spellCasters[0].spellcastingType ?? "spontaneous";
- }
-
- // aims to determine the class magic tradition for a spellcasting block
- getClassMagicTradition(caster) {
- const classCaster = [this.source.class, this.source.dualClass].includes(caster.name);
- const tradition = classCaster
- ? caster?.magicTradition
- : undefined;
- // if a caster tradition or no spellcasters, return divine
- if (tradition || this.source.spellCasters.length === 0) return tradition ?? "divine";
- // this spell caster type is not a class, determine class tradition based on ability
- const abilityTradition = this.source.spellCasters.find((c) =>
- [this.source.class, this.source.dualClass].includes(c.name)
- && c.ability === caster.ability
- );
- if (abilityTradition) return abilityTradition.magicTradition;
- // final fallback
- // if no type and multiple spell casters, then return the first spell casting type
- return this.source.spellCasters[0].magicTradition && this.source.spellCasters[0].magicTradition !== "focus"
- ? this.source.spellCasters[0].magicTradition
- : "divine";
- }
-
- async #generateSpellCaster(caster) {
- const isFocus = caster.magicTradition === "focus";
- const magicTradition = this.getClassMagicTradition(caster);
- const spellcastingType = isFocus ? "focus" : caster.spellcastingType;
- const flexible = false; // placeholder
-
- const name = isFocus ? `${src_utils.capitalize(magicTradition)} ${caster.name}` : caster.name;
-
- const spellcastingEntity = {
- ability: {
- value: caster.ability,
- },
- proficiency: {
- value: caster.proficiency / 2,
- },
- spelldc: {
- item: 0,
- },
- tradition: {
- value: magicTradition,
- },
- prepared: {
- value: spellcastingType,
- flexible,
- },
- slots: {
- slot0: {
- max: caster.perDay[0],
- prepared: {},
- value: caster.perDay[0],
- },
- slot1: {
- max: caster.perDay[1],
- prepared: {},
- value: caster.perDay[1],
- },
- slot2: {
- max: caster.perDay[2],
- prepared: {},
- value: caster.perDay[2],
- },
- slot3: {
- max: caster.perDay[3],
- prepared: {},
- value: caster.perDay[3],
- },
- slot4: {
- max: caster.perDay[4],
- prepared: {},
- value: caster.perDay[4],
- },
- slot5: {
- max: caster.perDay[5],
- prepared: {},
- value: caster.perDay[5],
- },
- slot6: {
- max: caster.perDay[6],
- prepared: {},
- value: caster.perDay[6],
- },
- slot7: {
- max: caster.perDay[7],
- prepared: {},
- value: caster.perDay[7],
- },
- slot8: {
- max: caster.perDay[8],
- prepared: {},
- value: caster.perDay[8],
- },
- slot9: {
- max: caster.perDay[9],
- prepared: {},
- value: caster.perDay[9],
- },
- slot10: {
- max: caster.perDay[10],
- prepared: {},
- value: caster.perDay[10],
- },
- },
- showUnpreparedSpells: { value: true },
- };
- const data = {
- _id: foundry.utils.randomID(),
- name,
- type: "spellcastingEntry",
- system: spellcastingEntity,
- };
- this.result.casters.push(data);
- return data;
- }
-
- async #processSpells() {
- const compendium = game.packs.get("pf2e.spells-srd");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
-
- const psiCompendium = game.packs.get("pf2e-psychic-amps.psychic-psi-cantrips");
- const psiIndex = psiCompendium ? await psiCompendium.getIndex({ fields: ["name", "type", "system.slug"] }) : undefined;
-
- for (const caster of this.source.spellCasters) {
- src_logger.debug("Generating caster for", caster);
- if (Number.isInteger(parseInt(caster.focusPoints))) this.result.focusPool += caster.focusPoints;
- const instance = await this.#generateSpellCaster(caster);
- src_logger.debug("Generated caster instance", instance);
-
- for (const spellSelection of caster.spells) {
- const level = spellSelection.spellLevel;
-
- for (const [i, spell] of spellSelection.list.entries()) {
- const spellName = spell.split("(")[0].trim();
- src_logger.debug("spell details", { spell, spellName, spellSelection, list: spellSelection.list });
-
- const psiMatch = psiIndex ? psiIndex.find((i) => i.name === spell) : undefined;
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(spellName));
- if (!indexMatch && !psiMatch) {
- src_logger.error(`Unable to match spell ${spell}`, { spell, spellName, spellSelection, caster, instance });
- this.bad.push({ pbName: spell, type: "spell", details: { originalName: spell, name: spellName, spellSelection, caster } });
- continue;
- }
-
- const doc = psiMatch
- ? await psiCompendium.getDocument(psiMatch._id)
- : await compendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.location.heightenedLevel = level;
- itemData.system.location.value = instance._id;
- this.result.spells.push(itemData);
-
- instance.system.slots[`slot${level}`].prepared[i] = { id: itemData._id };
- }
- }
- }
-
- setProperty(this.result.character, "system.resources.focus.max", this.result.focusPool);
- setProperty(this.result.character, "system.resources.focus.value", this.result.focusPool);
- }
-
- async #generateLores() {
- for (const lore of this.source.lores) {
- const data = {
- name: lore[0],
- type: "lore",
- system: {
- proficient: {
- value: lore[1] / 2,
- },
- featType: "",
- mod: {
- value: 0,
- },
- item: {
- value: 0,
- },
- },
- };
- this.result.lores.push(data);
- }
- }
-
- async #generateMoney() {
- const compendium = game.packs.get("pf2e.equipment-srd");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
- const moneyLookup = [
- { slug: "platinum-pieces", type: "pp" },
- { slug: "gold-pieces", type: "gp" },
- { slug: "silver-pieces", type: "sp" },
- { slug: "copper-pieces", type: "cp" },
- ];
-
- for (const lookup of moneyLookup) {
- const indexMatch = index.find((i) => i.system.slug === lookup.slug);
- if (indexMatch) {
- const doc = await compendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.quantity = this.source.money[lookup.type];
- this.result.money.push(itemData);
- }
- }
- }
-
- async #processFormulas() {
- const compendium = game.packs.get("pf2e.equipment-srd");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
- const uuids = [];
-
- for (const formulaSource of this.source.formula) {
- for (const formulaName of formulaSource.known) {
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(formulaName));
- if (!indexMatch) {
- src_logger.error(`Unable to match formula ${formulaName}`, { formulaSource, name: formulaName });
- this.bad.push({ pbName: formulaName, type: "formula", details: { formulaSource, name: formulaName } });
- continue;
- }
- const doc = await compendium.getDocument(indexMatch._id);
- uuids.push({ uuid: doc.uuid });
- }
- }
- setProperty(this.result.character, "system.crafting.formulas", uuids);
- }
-
- async #processFeats() {
- await this.#generateFeatItems("pf2e.feats-srd");
- await this.#generateFeatItems("pf2e.ancestryfeatures");
- await this.#generateSpecialItems("pf2e.ancestryfeatures");
- await this.#generateSpecialItems("pf2e.classfeatures");
- await this.#generateSpecialItems("pf2e.actionspf2e");
- }
-
- async #processEquipment() {
- await this.#generateEquipmentItems();
- await this.#generateWeaponItems();
- await this.#generateArmorItems();
- await this.#generateMoney();
- }
-
- async #generateTempActor(documents = []) {
- const actorData = mergeObject({ type: "character" }, this.result.character);
- actorData.name = "Mr Temp";
- const actor = await Actor.create(actorData);
- const currentState = duplicate(this.result);
-
- const currentItems = [
- ...(this.options.askForChoices ? this.autoFeats : []),
- ...currentState.feats,
- ...currentState.class,
- ...currentState.background,
- ...currentState.ancestry,
- ...currentState.heritage,
- ...currentState.deity,
- ...currentState.lores,
- ];
- for (const doc of documents) {
- if (!currentItems.some((d) => d._id === doc._id)) {
- currentItems.push(doc);
- }
- }
- try {
- const items = duplicate(currentItems).map((i) => {
- if (i.system.items) i.system.items = [];
- if (i.system.rules) i.system.rules = [];
- return i;
- });
-
- await actor.createEmbeddedDocuments("Item", items, { keepId: true });
- const ruleIds = currentItems.map((i) => i._id);
- const ruleUpdates = [];
- for (const [key, value] of Object.entries(this.allFeatureRules)) {
- if (ruleIds.includes(key)) {
- ruleUpdates.push({
- _id: key,
- system: {
- // rules: value,
- rules: value.filter((r) => ["GrantItem", "ChoiceSet", "RollOption"].includes(r.key)),
- },
- });
- }
- }
- // console.warn("rule updates", ruleUpdates);
- await actor.updateEmbeddedDocuments("Item", ruleUpdates);
-
- const itemUpdates = [];
- for (const [key, value] of Object.entries(this.autoAddedFeatureItems)) {
- itemUpdates.push({
- _id: `${key}`,
- system: {
- items: deepClone(value),
- },
- });
- }
-
- for (const doc of documents) {
- if (getProperty(doc, "system.rules")?.length > 0 && !ruleUpdates.some((r) => r._id === doc._id)) {
- ruleUpdates.push({
- _id: doc._id,
- system: {
- rules: deepClone(doc.system.rules),
- },
- });
- }
- }
-
- await actor.updateEmbeddedDocuments("Item", itemUpdates);
-
- src_logger.debug("Final temp actor", actor);
- } catch (err) {
- src_logger.error("Temp actor creation failed", {
- actor,
- documents,
- thisData: deepClone(this.result),
- actorData,
- err,
- currentItems,
- this: this,
- });
- }
- return actor;
- }
-
- async processCharacter() {
- if (!this.source) return;
- this.#prepare();
- await this.#processCore();
- await this.#processFormulas();
- await this.#processGenericCompendiumLookup("pf2e.deities", this.source.deity, "deity");
- await this.#processGenericCompendiumLookup("pf2e.backgrounds", this.source.background, "background");
- await this.#processGenericCompendiumLookup("pf2e.classes", this.source.class, "class");
- await this.#processGenericCompendiumLookup("pf2e.ancestries", this.source.ancestry, "ancestry");
- await this.#processGenericCompendiumLookup("pf2e.heritages", this.source.heritage, "heritage");
- await this.#detectGrantedFeatures();
- await this.#processFeats();
- await this.#processEquipment();
- await this.#processSpells();
- await this.#generateLores();
- }
-
- async #removeDocumentsToBeUpdated() {
- const moneyIds = this.actor.items.filter((i) =>
- i.type === "treasure"
- && ["Platinum Pieces", "Gold Pieces", "Silver Pieces", "Copper Pieces"].includes(i.name)
- );
- const classIds = this.actor.items.filter((i) => i.type === "class").map((i) => i._id);
- const deityIds = this.actor.items.filter((i) => i.type === "deity").map((i) => i._id);
- const backgroundIds = this.actor.items.filter((i) => i.type === "background").map((i) => i._id);
- const heritageIds = this.actor.items.filter((i) => i.type === "heritage").map((i) => i._id);
- const ancestryIds = this.actor.items.filter((i) => i.type === "ancestry").map((i) => i._id);
- const treasureIds = this.actor.items.filter((i) => i.type === "treasure" && !moneyIds.includes(i.id)).map((i) => i._id);
- const featIds = this.actor.items.filter((i) => i.type === "feat").map((i) => i._id);
- const actionIds = this.actor.items.filter((i) => i.type === "action").map((i) => i._id);
- const equipmentIds = this.actor.items.filter((i) =>
- i.type === "equipment" || i.type === "backpack" || i.type === "consumable"
- ).map((i) => i._id);
- const weaponIds = this.actor.items.filter((i) => i.type === "weapon").map((i) => i._id);
- const armorIds = this.actor.items.filter((i) => i.type === "armor").map((i) => i._id);
- const loreIds = this.actor.items.filter((i) => i.type === "lore").map((i) => i._id);
- const spellIds = this.actor.items.filter((i) => i.type === "spell" || i.type === "spellcastingEntry").map((i) => i._id);
- const formulaIds = this.actor.system.formulas;
-
- src_logger.debug("ids", {
- moneyIds,
- deityIds,
- classIds,
- backgroundIds,
- heritageIds,
- ancestryIds,
- treasureIds,
- featIds,
- actionIds,
- equipmentIds,
- weaponIds,
- armorIds,
- loreIds,
- spellIds,
- formulaIds,
- });
- // eslint-disable-next-line complexity
- const keepIds = this.actor.items.filter((i) =>
- (!this.options.addMoney && moneyIds.includes(i._id))
- || (!this.options.addClass && classIds.includes(i._id))
- || (!this.options.addDeity && deityIds.includes(i._id))
- || (!this.options.addBackground && backgroundIds.includes(i._id))
- || (!this.options.addHeritage && heritageIds.includes(i._id))
- || (!this.options.addAncestry && ancestryIds.includes(i._id))
- || (!this.options.addTreasure && treasureIds.includes(i._id))
- || (!this.options.addFeats && (featIds.includes(i._id) || actionIds.includes(i._id)))
- || (!this.options.addEquipment && equipmentIds.includes(i._id))
- || (!this.options.addWeapons && weaponIds.includes(i._id))
- || (!this.options.addArmor && armorIds.includes(i._id))
- || (!this.options.addLores && loreIds.includes(i._id))
- || (!this.options.addSpells && spellIds.includes(i._id))
- ).map((i) => i._id);
-
- const deleteIds = this.actor.items.filter((i) => !keepIds.includes(i._id)).map((i) => i._id);
- src_logger.debug("ids", {
- deleteIds,
- keepIds,
- });
- await this.actor.deleteEmbeddedDocuments("Item", deleteIds);
-
- }
-
- async #createActorEmbeddedDocuments() {
- if (this.options.addDeity) await this.actor.createEmbeddedDocuments("Item", this.result.deity, { keepId: true });
- if (this.options.addAncestry) await this.actor.createEmbeddedDocuments("Item", this.result.ancestry, { keepId: true });
- if (this.options.addHeritage) await this.actor.createEmbeddedDocuments("Item", this.result.heritage, { keepId: true });
- if (this.options.addBackground) await this.actor.createEmbeddedDocuments("Item", this.result.background, { keepId: true });
- if (this.options.addClass) await this.actor.createEmbeddedDocuments("Item", this.result.class, { keepId: true });
- if (this.options.addLores) await this.actor.createEmbeddedDocuments("Item", this.result.lores, { keepId: true });
- // for (const feat of this.result.feats.reverse()) {
- // console.warn(`creating ${feat.name}`, feat);
- // await this.actor.createEmbeddedDocuments("Item", [feat], { keepId: true });
- // }
- if (this.options.addFeats) await this.actor.createEmbeddedDocuments("Item", this.result.feats, { keepId: true });
- if (this.options.addSpells) {
- await this.actor.createEmbeddedDocuments("Item", this.result.casters, { keepId: true });
- await this.actor.createEmbeddedDocuments("Item", this.result.spells, { keepId: true });
- }
- if (this.options.addEquipment) await this.actor.createEmbeddedDocuments("Item", this.result.equipment, { keepId: true });
- if (this.options.addWeapons) await this.actor.createEmbeddedDocuments("Item", this.result.weapons, { keepId: true });
- if (this.options.addArmor) {
- await this.actor.createEmbeddedDocuments("Item", this.result.armor, { keepId: true });
- await this.actor.updateEmbeddedDocuments("Item", this.result.armor, { keepId: true });
- }
- if (this.options.addTreasure) await this.actor.createEmbeddedDocuments("Item", this.result.treasure, { keepId: true });
- if (this.options.addMoney) await this.actor.createEmbeddedDocuments("Item", this.result.money, { keepId: true });
- }
-
- async #restoreEmbeddedRuleLogic() {
- const importedItems = this.actor.items.map((i) => i._id);
- // Loop back over items and add rule and item progression data back in.
- if (!this.options.askForChoices) {
- src_logger.debug("Restoring logic", { currentActor: duplicate(this.actor) });
- const ruleUpdates = [];
- for (const [key, value] of Object.entries(this.autoAddedFeatureRules)) {
- if (importedItems.includes(key)) {
- ruleUpdates.push({
- _id: `${key}`,
- system: {
- rules: deepClone(value),
- },
- });
- }
- }
- src_logger.debug("Restoring rule logic", ruleUpdates);
- await this.actor.updateEmbeddedDocuments("Item", ruleUpdates);
-
- const itemUpdates = [];
- for (const [key, value] of Object.entries(this.autoAddedFeatureItems)) {
- if (importedItems.includes(key)) {
- itemUpdates.push({
- _id: `${key}`,
- system: {
- items: deepClone(value),
- },
- });
- }
- }
- src_logger.debug("Restoring granted item logic", itemUpdates);
- await this.actor.updateEmbeddedDocuments("Item", itemUpdates);
- }
- }
-
- async updateActor() {
- await this.#removeDocumentsToBeUpdated();
-
- if (!this.options.addName) {
- delete this.result.character.name;
- delete this.result.character.prototypeToken.name;
- }
- if (!this.options.addFormulas) {
- delete this.result.character.system.formulas;
- }
-
- src_logger.debug("Generated result", this.result);
- await this.actor.update(this.result.character);
- await this.#createActorEmbeddedDocuments();
- await this.#restoreEmbeddedRuleLogic();
- }
-
- async postImportCheck() {
- const badClass = this.options.addClass
- ? this.bad.filter((b) => b.type === "class").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Class")}: ${b.pbName}</li>`)
- : [];
- const badHeritage = this.options.addHeritage
- ? this.bad.filter((b) => b.type === "heritage").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Heritage")}: ${b.pbName}</li>`)
- : [];
- const badAncestry = this.options.addAncestry
- ? this.bad.filter((b) => b.type === "ancestry").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Ancestry")}: ${b.pbName}</li>`)
- : [];
- const badBackground = this.options.addBackground
- ? this.bad.filter((b) => b.type === "background").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Background")}: ${b.pbName}</li>`)
- : [];
- const badDeity = this.options.addDeity
- ? this.bad.filter((b) => b.type === "deity" && b.pbName !== "Not set").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Deity")}: ${b.pbName}</li>`)
- : [];
- const badFeats = this.options.addFeats
- ? this.bad.filter((b) => b.type === "feat").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Feats")}: ${b.pbName}</li>`)
- : [];
- const badFeats2 = this.options.addFeats
- ? Object.values(this.check).filter((b) =>
- (b.type === "feat" || b.type === "special")
- && this.parsed.feats.concat(this.parsed.specials).some((f) => f.name === b.details.name && !f.added)
- ).map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Feats")}: ${b.details.name}</li>`)
- : [];
- const badEquipment = this.options.addEquipment
- ? this.bad.filter((b) => b.type === "equipment").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Equipment")}: ${b.pbName}</li>`)
- : [];
- const badWeapons = this.options.addWeapons
- ? this.bad.filter((b) => b.type === "weapons").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Weapons")}: ${b.pbName}</li>`)
- : [];
- const badArmor = this.options.addArmor
- ? this.bad.filter((b) => b.type === "armor").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Armor")}: ${b.pbName}</li>`)
- : [];
- const badSpellcasting = this.options.addSpells
- ? this.bad.filter((b) => b.type === "spellcasting").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Spellcasting")}: ${b.pbName}</li>`)
- : [];
- const badSpells = this.options.addSpells
- ? this.bad.filter((b) => b.type === "spells").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Spells")}: ${b.pbName}</li>`)
- : [];
- const badFamiliars = this.options.addFamiliars
- ? this.bad.filter((b) => b.type === "familiars").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Familiars")}: ${b.pbName}</li>`)
- : [];
- const badFormulas = this.options.addFormulas
- ? this.bad.filter((b) => b.type === "formulas").map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Formulas")}: ${b.pbName}</li>`)
- : [];
- const totalBad = [
- ...badClass,
- ...badAncestry,
- ...badHeritage,
- ...badBackground,
- ...badDeity,
- ...badFeats,
- ...badFeats2,
- ...badEquipment,
- ...badWeapons,
- ...badArmor,
- ...badSpellcasting,
- ...badSpells,
- ...badFamiliars,
- ...badFormulas,
- ];
-
- let warning = "";
-
- if (totalBad.length > 0) {
- warning += `<p>${game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.MissingItemsOpen")}</p><ul>${totalBad.join("\n")}</ul><br>`;
- }
- if (this.result.focusPool > 0) {
- warning += `<strong>${game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.FocusSpells")}</strong><br>`;
- }
-
- src_logger.debug("Bad thing check", {
- badClass,
- badAncestry,
- badHeritage,
- badBackground,
- badDeity,
- badFeats,
- badFeats2,
- badEquipment,
- badWeapons,
- badArmor,
- badSpellcasting,
- badSpells,
- badFamiliars,
- badFormulas,
- totalBad,
- count: totalBad.length,
- focusPool: this.result.focusPool,
- warning,
- });
-
- if (totalBad.length > 0 || this.result.focusPool > 0) {
- ui.notifications.warn(game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.CompletedWithNotes"));
- new Dialog({
- title: game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.ImportNotes"),
- content: warning,
- buttons: {
- yes: {
- icon: "<i class='fas fa-check'></i>",
- label: game.i18n.localize("pathmuncher.Labels.Finished"),
- },
- },
- default: "yes",
- }).render(true);
- } else {
- ui.notifications.info(game.i18n.localize("pathmuncher.Dialogs.Pathmuncher.CompletedSuccess"));
- }
- }
- }
-
- ;// CONCATENATED MODULE: ./src/app/PetShop.js
- /* eslint-disable no-await-in-loop */
- /* eslint-disable no-continue */
-
-
-
-
-
- /**
- * The PetShop class looks for familiars in a Pathmunch data set and creates/updates as appropriate.
- */
- class PetShop {
-
-
- constructor ({ parent, pathbuilderJson } = {}) {
- this.parent = parent;
- this.pathbuilderJson = pathbuilderJson;
-
- this.result = {
- pets: [],
- features: {},
- };
-
- this.bad = {};
- this.folders = {};
- }
-
-
- async ensureFolder(type) {
- const folderName = game.i18n.localize(`${constants.MODULE_NAME}.Labels.${type}s`);
- this.folders[type] = await src_utils.getOrCreateFolder(parent.folder, "Actor", folderName);
- }
-
- async #existingPetCheck(familiarName, type) {
- const existingPet = game.actors.find((a) =>
- a.type === type.toLowerCase()
- && a.name === familiarName
- && a.system.master.id === this.parent._id
- );
-
- if (existingPet) return existingPet.toObject();
-
- const actorData = {
- type: type.toLowerCase(),
- name: familiarName,
- system: {
- master: {
- id: this.parent._id,
- ability: this.parent.system.details.keyability.value,
- },
- },
- prototypeToken: {
- name: familiarName,
- },
- folder: this.folders[type].id,
- };
- const actor = await Actor.create(actorData);
- return actor.toObject();
-
- }
-
- #buildCore(petData) {
- setProperty(petData, "system.attributes.value", this.parent.system.details.level.value * 5);
- return petData;
- }
-
- async #generatePetFeatures(pet, json) {
- const compendium = game.packs.get("pf2e.familiar-abilities");
- const index = await compendium.getIndex({ fields: ["name", "type", "system.slug"] });
- this.result.features[pet._id] = [];
- this.bad[pet._id] = [];
-
- for (const featureName of json.abilities) {
- const indexMatch = index.find((i) => i.system.slug === game.pf2e.system.sluggify(featureName));
- if (!indexMatch) {
- src_logger.warn(`Unable to match pet feature ${featureName}`, { pet, json, name: featureName });
- this.bad[pet._id].push({ pbName: featureName, type: "feature", details: { pet, json, name: featureName } });
- continue;
- }
- const doc = (await compendium.getDocument(indexMatch._id)).toObject();
- doc._id = foundry.utils.randomID();
- this.result.features[pet._id].push(doc);
- }
- }
-
- async buildPet(json) {
- const name = json.name === json.type || !json.name.includes("(")
- ? `${this.parent.name}'s ${json.type}`
- : json.name.split("(")[1].split(")")[0];
- const petData = await this.#existingPetCheck(name, json.type);
- const pet = this.#buildCore(petData);
- await this.#generatePetFeatures(pet, json);
- this.result.pets.push(pet);
- }
-
- async updatePets() {
- for (const petData of this.result.pets) {
- const actor = game.actors.get(petData._id);
- await actor.deleteEmbeddedDocuments("Item", [], { deleteAll: true });
- await actor.update(petData);
- await actor.createEmbeddedDocuments("Item", this.result.features[petData._id], { keepId: true });
- }
- }
-
- async processPets() {
-
- for (const petJson of this.pathbuilderJson.pets) {
- // only support familiars at this time
- if (petJson.type !== "Familiar") {
- src_logger.warn(`Pets with type ${petJson.type} are not supported at this time`);
- continue;
- }
- await this.ensureFolder(petJson.type);
- await this.buildPet(petJson);
- }
-
- await this.updatePets();
-
- src_logger.debug("Pets", {
- results: this.results,
- bad: this.bad,
- });
- }
-
- async addPetEffects() {
- const features = [];
- for (const petData of this.result.pets) {
- for (const feature of this.result.features[petData._id].filter((f) => f.system.rules?.some((r) => r.key === "ActiveEffectLike"))) {
- if (!this.parent.items.some((i) => i.type === "effect" && i.system.slug === feature.system.slug)) {
- features.push(feature);
- }
- }
- }
- await this.parent.createEmbeddedDocuments("Item", features);
- }
-
- }
-
- ;// CONCATENATED MODULE: ./src/app/PathmuncherImporter.js
-
-
-
-
-
-
- class PathmuncherImporter extends FormApplication {
-
- constructor(options, actor) {
- super(options);
- this.actor = game.actors.get(actor.id ? actor.id : actor._id);
- this.backup = duplicate(this.actor);
- this.mode = "number";
- }
-
- static get defaultOptions() {
- const options = super.defaultOptions;
- options.title = game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.PathmuncherImporter.Title`);
- options.template = `${constants.PATH}/templates/pathmuncher.hbs`;
- options.classes = ["pathmuncher"];
- options.id = "pathmuncher";
- options.width = 400;
- options.closeOnSubmit = false;
- options.tabs = [{ navSelector: ".tabs", contentSelector: "form", initial: "number" }];
- return options;
- }
-
- /** @override */
- async getData() {
- const flags = src_utils.getFlags(this.actor);
-
- return {
- flags,
- id: flags?.pathbuilderId ?? "",
- actor: this.actor,
- };
- }
-
- /** @override */
- activateListeners(html) {
- super.activateListeners(html);
- $("#pathmuncher").css("height", "auto");
-
- $(html)
- .find('.item')
- .on("click", (event) => {
- if (!event.target?.dataset?.tab) return;
- this.mode = event.target.dataset.tab;
- });
- }
-
- static _updateProgress(total, count, type) {
- const localizedType = `pathmuncher.Label.${type}`;
- $(".import-progress-bar")
- .width(`${Math.trunc((count / total) * 100)}%`)
- .html(
- `<span>${game.i18n.localize("pathmuncher.Label.Working")} (${game.i18n.localize(localizedType)})...</span>`
- );
- }
-
- async _updateObject(event, formData) {
- const pathbuilderId = formData.textBoxBuildID;
- const options = {
- pathbuilderId,
- addMoney: formData.checkBoxMoney,
- addFeats: formData.checkBoxFeats,
- addSpells: formData.checkBoxSpells,
- addEquipment: formData.checkBoxEquipment,
- addTreasure: formData.checkBoxTreasure,
- addLores: formData.checkBoxLores,
- addWeapons: formData.checkBoxWeapons,
- addArmor: formData.checkBoxArmor,
- addDeity: formData.checkBoxDeity,
- addName: formData.checkBoxName,
- addClass: formData.checkBoxClass,
- addBackground: formData.checkBoxBackground,
- addHeritage: formData.checkBoxHeritage,
- addAncestry: formData.checkBoxAncestry,
- addFamiliars: formData.checkBoxFamiliars,
- addFormulas: formData.checkBoxFormulas,
- askForChoices: formData.checkBoxAskForChoices,
- };
- src_logger.debug("Pathmuncher options", options);
-
- await src_utils.setFlags(this.actor, options);
-
- const pathmuncher = new Pathmuncher(this.actor, options);
- if (this.mode === "number") {
- await pathmuncher.fetchPathbuilder(pathbuilderId);
- } else if (this.mode === "json") {
- try {
- const jsonData = JSON.parse(formData.textBoxBuildJSON);
- pathmuncher.source = jsonData.build;
- } catch (err) {
- ui.notifications.error("Unable to parse JSON data");
- return;
- }
- }
-
- src_logger.debug("Pathmuncher Source", pathmuncher.source);
- await pathmuncher.processCharacter();
- src_logger.debug("Post processed character", pathmuncher);
- await pathmuncher.updateActor();
- src_logger.debug("Final import details", {
- actor: this.actor,
- pathmuncher,
- options,
- pathbuilderSource: pathmuncher.source,
- pathbuilderId,
- });
-
- if (options.addFamiliars) {
- const petShop = new PetShop({ parent: this.actor, pathbuilderJson: pathmuncher.source });
- await petShop.processPets();
- await petShop.addPetEffects();
- }
- this.close();
- await pathmuncher.postImportCheck();
- }
-
- }
-
- ;// CONCATENATED MODULE: ./src/hooks/api.js
-
-
-
-
-
-
-
- function registerAPI() {
- game.modules.get(constants.MODULE_NAME).api = {
- Pathmuncher: Pathmuncher,
- PathmuncherImporter: PathmuncherImporter,
- data: {
- generateFeatMap: FEAT_RENAME_MAP,
- equipment: EQUIPMENT_RENAME_MAP,
- restrictedEquipment: RESTRICTED_EQUIPMENT,
- feats: FEAT_RENAME_MAP(),
- },
- utils: src_utils,
- CONSTANTS: constants,
- };
- }
-
- ;// CONCATENATED MODULE: ./src/hooks/settings.js
-
-
- async function resetSettings() {
- for (const [name, data] of Object.entries(constants.GET_DEFAULT_SETTINGS())) {
- // eslint-disable-next-line no-await-in-loop
- await game.settings.set(constants.MODULE_NAME, name, data.default);
- }
- window.location.reload();
- }
-
- class ResetSettingsDialog extends FormApplication {
- constructor(...args) {
- super(...args);
- // eslint-disable-next-line no-constructor-return
- return new Dialog({
- title: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.ResetSettings.Title`),
- content: `<p class="${constants.FLAG_NAME}-dialog-important">${game.i18n.localize(
- `${constants.FLAG_NAME}.Dialogs.ResetSettings.Content`
- )}</p>`,
- buttons: {
- confirm: {
- icon: '<i class="fas fa-check"></i>',
- label: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.ResetSettings.Confirm`),
- callback: () => {
- resetSettings();
- },
- },
- cancel: {
- icon: '<i class="fas fa-times"></i>',
- label: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.ResetSettings.Cancel`),
- },
- },
- default: "cancel",
- });
- }
- }
-
- function registerSettings() {
- game.settings.registerMenu(constants.MODULE_NAME, "resetToDefaults", {
- name: `${constants.FLAG_NAME}.Settings.Reset.Title`,
- label: `${constants.FLAG_NAME}.Settings.Reset.Label`,
- hint: `${constants.FLAG_NAME}.Settings.Reset.Hint`,
- icon: "fas fa-refresh",
- type: ResetSettingsDialog,
- restricted: true,
- });
-
- for (const [name, data] of Object.entries(constants.GET_DEFAULT_SETTINGS())) {
- game.settings.register(constants.MODULE_NAME, name, data);
- }
-
- }
-
- ;// CONCATENATED MODULE: ./src/hooks/sheets.js
-
-
-
-
- function registerSheetButton() {
-
- const trustedUsersOnly = src_utils.setting(constants.SETTINGS.RESTRICT_TO_TRUSTED);
- if (trustedUsersOnly && !game.user.isTrusted) return;
-
- /**
- * Character sheets
- */
- const pcSheetNames = Object.values(CONFIG.Actor.sheetClasses.character)
- .map((sheetClass) => sheetClass.cls)
- .map((sheet) => sheet.name);
-
- pcSheetNames.forEach((sheetName) => {
- Hooks.on("render" + sheetName, (app, html, data) => {
- // only for GMs or the owner of this character
- if (!data.owner || !data.actor) return;
-
- const button = $(`<a class="pathmuncher-open" title="${constants.MODULE_FULL_NAME}"><i class="fas fa-hat-wizard"></i> Pathmuncher</a>`);
-
- button.click(() => {
- const muncher = new PathmuncherImporter(PathmuncherImporter.defaultOptions, data.actor);
- muncher.render(true);
- });
-
- html.closest('.app').find('.pathmuncher-open').remove();
- let titleElement = html.closest('.app').find('.window-title');
- if (!app._minimized) button.insertAfter(titleElement);
- });
- });
-
- }
-
- ;// CONCATENATED MODULE: ./src/module.js
-
-
-
-
- Hooks.once("init", () => {
- registerSettings();
- });
-
- Hooks.once("ready", () => {
- registerSheetButton();
- registerAPI();
- });
-
- /******/ })()
- ;
- //# sourceMappingURL=main.js.map
|