|
|
- /******/ (() => { // 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",
- USE_CUSTOM_COMPENDIUM_MAPPINGS: "use-custom-compendium-mappings",
- CUSTOM_COMPENDIUM_MAPPINGS: "custom-compendium-mappings",
- USE_IMMEDIATE_DEEP_DIVE: "use-immediate-deep-dive",
- },
-
- FEAT_PRIORITY: [
- "Heritage",
- "Heritage Feat",
- "Ancestry",
- "Ancestry Feat",
- "Background",
- "Background Feat",
- "Class Feat",
- "Skill Feat",
- "General Feat",
- "Awarded Feat",
- ],
-
- ACTOR_FLAGS: {
- pathbuilderId: undefined,
- addFeats: true,
- addEquipment: true,
- addBackground: true,
- addHeritage: true,
- addAncestry: true,
- addSpells: true,
- adjustBlendedSlots: true,
- addMoney: true,
- addTreasure: true,
- addLores: true,
- addWeapons: true,
- addArmor: true,
- addDeity: true,
- addName: true,
- addClass: true,
- addFamiliars: true,
- addFormulas: true,
- },
-
- CORE_COMPENDIUM_MAPPINGS: {
- feats: [
- "pf2e.feats-srd",
- "pf2e-playtest-data.war-of-immortals-playtest-class-feats",
- "pf2e-legacy-content.feats-legacy",
- ],
- ancestryFeatures: ["pf2e.ancestryfeatures", "pf2e-legacy-content.ancestry-features-legacy"],
- classFeatures: [
- "pf2e.classfeatures",
- "pf2e-playtest-data.war-of-immortals-playtest-class-features",
- "pf2e-legacy-content.class-features-legacy",
- ],
- actions: [
- "pf2e.actionspf2e",
- "pf2e-playtest-data.war-of-immortals-playtest-actions",
- "pf2e-legacy-content.actions-legacy",
- ],
- spells: [
- "pf2e-psychic-amps.psychic-psi-cantrips",
- "pf2e.spells-srd",
- "pf2e-playtest-data.war-of-immortals-playtest-spells",
- "pf2e-legacy-content.spells-legacy",
- ],
- classes: [
- "pf2e.classes",
- "pf2e-playtest-data.war-of-immortals-playtest-classes",
- "pf2e-legacy-content.classes-legacy",
- ],
- ancestries: ["pf2e.ancestries", "pf2e-legacy-content.ancestries-legacy"],
- heritages: ["pf2e.heritages", "pf2e-legacy-content.heritages-legacy"],
- equipment: ["pf2e.equipment-srd", "pf2e-legacy-content.equipment-legacy"],
- formulas: ["pf2e.equipment-srd", "pf2e-legacy-content.formulas-legacy"],
- deities: ["pf2e.deities", "pf2e-legacy-content.deities-legacy"],
- backgrounds: ["pf2e.backgrounds", "pf2e-legacy-content.backgrounds-legacy"],
- },
-
- 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.USE_CUSTOM_COMPENDIUM_MAPPINGS]: {
- name: `${CONSTANTS.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Name`,
- scope: "world",
- config: false,
- type: Boolean,
- default: false,
- },
-
- [CONSTANTS.SETTINGS.USE_IMMEDIATE_DEEP_DIVE]: {
- name: `${CONSTANTS.FLAG_NAME}.Settings.UseImmediateDeepDive.Name`,
- scope: "world",
- config: false,
- type: Boolean,
- default: true,
- },
-
- [CONSTANTS.SETTINGS.CUSTOM_COMPENDIUM_MAPPINGS]: {
- scope: "world",
- config: false,
- type: Object,
- default: {
- feats: [
- "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-feats",
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-features",
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-feats",
- "clerics.clerics-feats",
- "clerics.clerics-features",
- "pf2e.feats-srd",
- "pf2e-playtest-data.war-of-immortals-playtest-class-feats",
- ],
- ancestryFeatures: [
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-features",
- "pf2e.ancestryfeatures",
- ],
- classFeatures: [
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-features",
- "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-feats",
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-feats",
- "clerics.clerics-doctrines",
- "clerics.clerics-feats",
- "clerics.clerics-features",
- "pf2e.classfeatures",
- "pf2e-playtest-data.war-of-immortals-playtest-class-features",
- ],
- actions: ["pf2e.actionspf2e", "pf2e-playtest-data.war-of-immortals-playtest-actions"],
- spells: ["pf2e-psychic-amps.psychic-psi-cantrips", "pf2e.spells-srd", "pf2e-playtest-data.war-of-immortals-playtest-spells"],
- classes: ["clerics.clerics-features", "pf2e.classes", "pf2e-playtest-data.war-of-immortals-playtest-classes"],
- ancestries: [
- "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-ancestry",
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-ancestries",
- "pf2e.ancestries",
- ],
- heritages: [
- "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-heritages",
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-heritages",
- "pf2e.heritages",
- ],
- equipment: [
- "battlezoo-ancestries-dragons-pf2e.pf2e-battlezoo-dragon-equipment",
- "battlezoo-ancestries-year-of-monsters-pf2e.yom-equipment",
- "pf2e.equipment-srd"
- ],
- formulas: ["pf2e.equipment-srd"],
- deities: ["clerics.clerics-deities", "pf2e.deities"],
- backgrounds: ["pf2e.backgrounds"],
- },
- },
-
- [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: false,
- },
-
- // 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/utils.js
-
-
- const utils = {
-
- isObject: (obj) => {
- return typeof obj === 'object' && !Array.isArray(obj) && obj !== null;
- },
-
- isString: (str) => {
- return typeof str === 'string' || str instanceof String;
- },
-
- 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, constants.SETTINGS[key]);
- },
-
- updateSetting: async (key, value) => {
- return game.settings.set(constants.MODULE_NAME, constants.SETTINGS[key], value);
- },
-
- getFlags: (actor) => {
- const flags = actor.flags[constants.FLAG_NAME]
- ? actor.flags[constants.FLAG_NAME]
- : constants.ACTOR_FLAGS;
- 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;
- }
- },
-
- allowDualClasses: () => {
- return (isNewerVersion("5.9.0", game.version) && game.settings.get("pf2e", "dualClassVariant"));
- // || (!isNewerVersion("5.9.0", game.version) && when remaster supports dualclass then add here
- },
-
- allowAncestryParagon: () => {
- return (isNewerVersion("5.9.0", game.version) && game.settings.get("pf2e", "ancestryParagonVariant"));
- }
-
- };
-
-
- /* harmony default export */ const src_utils = (utils);
-
- ;// CONCATENATED MODULE: ./src/logger.js
-
-
-
- const logger = {
- _showMessage: (logLevel, data) => {
- if (!logLevel || !data || typeof logLevel !== "string") {
- return false;
- }
-
- const setting = src_utils.setting("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/data/equipment.js
- const SWAPS = [
- /^(Greater) (.*)/,
- /^(Lesser) (.*)/,
- /^(Major) (.*)/,
- /^(Moderate) (.*)/,
- /^(Standard) (.*)/,
- ];
-
- const POSTFIX_PB_REMOVALS = [
- /(.*) (- Melee)$/,
- /(.*) (- Ranged)$/,
- /(Charm of Resistance .*) - (.*)/,
- ];
-
- const PARENTHESIS = [
- /^(.*) \((.*)\)$/,
- ];
-
- const SPLITS = [
- // /^(.*) - (.*)/,
- ];
-
- const SPLITS_INVERT = [
- /^(.*): (.*)/,
- ];
-
- const REPLACES = [
- { pbName: "Ring of Energy Resistance", foundryName: "Charm of Resistance" },
- { pbName: "Feather Token", foundryName: "Marvelous Miniatures" },
- { pbName: "Goggles of Night", foundryName: "Obsidian Goggles" },
- ];
-
- // this equipment is named differently in foundry vs pathbuilder
- const EQUIPMENT_RENAME_STATIC_MAP = [
- { pbName: "Inventor Power Suit", foundryName: "Power Suit" },
- { pbName: "Inventor Power Suit (Heavy)", foundryName: "Power Suit" },
- { pbName: "Inventor Subterfuge Suit", foundryName: "Subterfuge Suit" },
- { 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" },
- { pbName: "Hide", foundryName: "Hide Armor" },
- { pbName: "Soverign Glue", foundryName: "Sovereign Glue" },
- { pbName: "Axe Musket - Melee", foundryName: "Axe Musket" },
- { pbName: "Axe Musket - Ranged", foundryName: "Axe Musket" },
- { pbName: "Extendible Pincer", foundryName: "Extendable Pincer" },
- { pbName: "Clothing (Explorer's)", foundryName: "Explorer's Clothing" },
- { pbName: "Street Preacher [Placeholder]", foundryName: "Street Preacher" },
- { pbName: "Repair Kit", foundryName: "Repair Toolkit" },
- { pbName: "Repair Kit (Superb)", foundryName: "Repair Toolkit (Superb)" },
- { pbName: "Alchemist's Tools", foundryName: "Alchemist's Toolkit" },
- { pbName: "Healer's Tools", foundryName: "Healer's Toolkit" },
- { pbName: "Healer's Tools (Expanded)", foundryName: "Healer's Toolkit (Expanded)" },
- { pbName: "Thieves' Tools", foundryName: "Thieves' Toolkit" },
- { pbName: "Thieves' Tools (Infiltrator)", foundryName: "Thieves' Toolkit (Infiltrator)" },
- { pbName: "Thieves' Tools (Infiltrator Picks)", foundryName: "Thieves' Toolkit (Infiltrator Picks)" },
- { pbName: "Artisan's Tools", foundryName: "Artisan's Toolkit" },
- { pbName: "Artisan's Tools (Sterling)", foundryName: "Artisan's Toolkit (Sterling)" },
-
- { pbName: "Aeon Stone (Dull Grey)", foundryName: "Aeon Stone (Consumed)" },
- { pbName: "Aeon Stone (Clear Spindle)", foundryName: "Aeon Stone (Nourishing)" },
- { pbName: "Aeon Stone (Tourmaline Sphere)", foundryName: "Aeon Stone (Delaying)" },
- { pbName: "Aeon Stone (Orange Prism)", foundryName: "Aeon Stone (Amplifying)" },
- { pbName: "Bag of Holding", foundryName: "Spacious Pouch" },
- { pbName: "Barkskin Potion", foundryName: "Oak Potion" },
- { pbName: "Boots of Speed", foundryName: "Propulsive Boots" },
- { pbName: "Bracers of Armor", foundryName: "Bands of Force" },
- { pbName: "Broom of Flying", foundryName: "Flying Broomstick" },
- { pbName: "Dagger of Venom", foundryName: "Serpent Dagger" },
- // these are actually matched to energy type witch Pathbuilder does not support
- { pbName: "Dragon's Breath Potion (Young)", foundryName: "Energy Breath Potion (Lesser)" },
- { pbName: "Dragon's Breath Potion (Adult)", foundryName: "Energy Breath Potion (Moderate)" },
- { pbName: "Dragon's Breath Potion (Wyrm)", foundryName: "Energy Breath Potion (Greater)" },
- { pbName: "Druid's Vestments", foundryName: "Living Mantle" },
- { pbName: "Everburning Torch", foundryName: "Everlight Crystal" },
- { pbName: "Eyes of the Eagle", foundryName: "Eyes of the Cat" },
- { pbName: "Feather Token (Chest)", foundryName: "Marvelous Miniatures (Chest)" },
- { pbName: "Feather Token (Ladder)", foundryName: "Marvelous Miniatures (Ladder)" },
- { pbName: "Feather Token (Swan Boat)", foundryName: "Marvelous Miniatures (Boat)" },
- { pbName: "Flame Tongue", foundryName: "Searing Blade" },
- { pbName: "Gloves of Storing", foundryName: "Retrieval Belt" },
- { pbName: "Goggles of Night", foundryName: "Obsidian Goggles" },
- { pbName: "Goggles of Night (Greater)", foundryName: "Obsidian Goggles (Greater)" },
- { pbName: "Goggles of Night (Major)", foundryName: "Obsidian Goggles (Major)" },
- { pbName: "Hat of Disguise", foundryName: "Masquerade Scarf" },
- { pbName: "Hat of Disguise (Greater)", foundryName: "Masquerade Scarf (Greater)" },
- { pbName: "Horn of Fog", foundryName: "Cloud Pouch" },
- { pbName: "Horseshoes of Speed", foundryName: "Alacritous Horseshoes" },
- { pbName: "Javelin of Lightning", foundryName: "Trident of Lightning" },
- { pbName: "Potion of Expeditious Retreat", foundryName: "Potion of Emergency Escape" },
- { pbName: "Ring of Energy Resistance (Greater)", foundryName: "Charm of Resistance (Greater)" },
- { pbName: "Ring of Energy Resistance (Major)", foundryName: "Charm of Resistance (Major)" },
- { pbName: "Silversheen", foundryName: "Silver Salve" },
- { pbName: "Smokestick (Lesser)", foundryName: "Smoke Ball (Lesser)" },
- { pbName: "Smokestick (Greater)", foundryName: "Smoke Ball (Greater)" },
- { pbName: "Sunrod", foundryName: "Glow Rod" },
- { pbName: "Tanglefoot Bag (Lesser)", foundryName: "Glue Bomb (Lesser)" },
- { pbName: "Tanglefoot Bag (Moderate)", foundryName: "Glue Bomb (Moderate)" },
- { pbName: "Tanglefoot Bag (Major)", foundryName: "Glue Bomb (Major)" },
- { pbName: "Tanglefoot Bag (Greater)", foundryName: "Glue Bomb (Greater)" },
- { pbName: "Tindertwig", foundryName: "Matchstick" },
- { pbName: "Owlbear Claw", foundryName: "Predator's Claw" },
- { pbName: "Wand of Manifold Missiles", foundryName: "Wand of Shardstorm" },
- { pbName: "Wand of Manifold Missiles (1st-Level Spell)", foundryName: "Wand of Shardstorm (1st-Rank Spell)" },
- { pbName: "Wand of Manifold Missiles (3rd-Level Spell)", foundryName: "Wand of Shardstorm (3rd-Rank Spell)" },
- { pbName: "Wand of Manifold Missiles (5th-Level Spell)", foundryName: "Wand of Shardstorm (5th-Rank Spell)" },
- { pbName: "Wand of Manifold Missiles (7th-Level Spell)", foundryName: "Wand of Shardstorm (7th-Rank Spell)" },
-
- ];
-
- function dynamicNamesSteps(pbName) {
- const 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 SWAPS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: `${match[2]} (${match[1]})`, details: match[2] });
- }
- }
- 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 SPLITS_INVERT) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[1], details: match[2] });
- }
- }
- for (const reg of PARENTHESIS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[1], details: match[2] });
- }
- }
- return result;
- }
-
- function generateDynamicNames(pbName) {
- const result = [];
- // if we have a hardcoded map, don't return here
- const basicResults = EQUIPMENT_RENAME_STATIC_MAP.filter((e) => e.pbName === pbName);
- if (basicResults.length > 0) {
- result.push(...basicResults);
- }
-
- for (const replace of REPLACES) {
- if (pbName.includes(replace.pbName)) {
- const replaced = pbName.replace(replace.pbName, replace.foundryName);
- result.push(...dynamicNamesSteps(replaced));
- result.push({ pbName, foundryName: replaced });
- }
- }
-
- if (result.length > 0) {
- return result;
- }
-
- result.push(...dynamicNamesSteps(pbName));
- 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",
- "Inventor Power Suit",
- "Inventor Power Suit (Heavy)",
- "Inventor Subterfuge Suit",
- ];
-
- const IGNORED_DISPLAY_POSTFIX = [
- /(.*) - Melee$/,
- /(.*) - Ranged$/,
- ];
-
- function IGNORED_EQUIPMENT_DISPLAY(pbName) {
- for (const reg of IGNORED_DISPLAY_POSTFIX) {
- const match = reg.test(pbName);
- if (match === true) return true;
- }
- return false;
- }
-
- const GRANTED_ITEMS_LIST = [
- "Inventor Power Suit",
- "Inventor Power Suit (Heavy)",
- "Inventor Subterfuge Suit",
- ];
-
- ;// CONCATENATED MODULE: ./src/data/features.js
- // these are features which are named differently in pathbuilder to foundry
-
-
-
- const SKILL_LOOKUP = {
- "acrobatics": "acr",
- "arcana": "arc",
- "athletics": "ath",
- "crafting": "cra",
- "deception": "dec",
- "diplomacy": "dip",
- "intimidation": "itm",
- "medicine": "med",
- "nature": "nat",
- "occultism": "occ",
- "performance": "prf",
- "religion": "rel",
- "society": "soc",
- "stealth": "ste",
- "survival": "sur",
- "thievery": "thi",
- };
-
- const features_POSTFIX_PB_REMOVALS = [
- /(.*) (Racket)$/,
- /(.*) (Style)$/,
- /(.*) (Initiate Benefit)$/,
- /(.*) Mystery$/,
- // Cleric +
- /(.*) (Doctrine)$/,
- /(.*) (Element)$/,
- /(.*) (Impulse Junction)$/,
- /(.*) (Gate Junction:).*$/,
- /(.*) (Patron)$/,
- ];
-
- const PREFIX_PB_REMOVALS = [
- /^(Arcane Thesis): (.*)/,
- /^(Arcane School): (.*)/,
- /^(The) (.*)/,
- // Cleric +
- /^(Blessing): (.*)/,
- /^(Empiricism) Selected Skill: (.*)/,
- ];
-
- const POSTFIX_PB_SPLIT_AND_KEEP = [
- /(.*) (Impulse Junction)$/,
- /(.*) Gate Junction: (.*)$/,
- ];
-
- const features_PARENTHESIS = [
- /^(.*) \((.*)\)$/,
- ];
-
- const features_SPLITS = [
- /^(.*): (.*)/,
- ];
-
- const features_SWAPS = [
- /^(Greater) (.*)/,
- /^(Lesser) (.*)/,
- /^(Major) (.*)/,
- /^(Moderate) (.*)/,
- /^(Standard) (.*)/,
- ];
-
- const FEAT_RENAME_STATIC_MAP = [
- { pbName: "Academic", foundryName: "Ustalavic Academic" },
- { pbName: "Academic (Arcana)", foundryName: "Magaambya Academic" },
- { pbName: "Academic (Nature)", foundryName: "Magaambya Academic" },
- { 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: "Canoneer", foundryName: "Cannoneer" },
- { pbName: "Cave Climber Kobold", foundryName: "Caveclimber Kobold" },
- { pbName: "Child of Squalor", foundryName: "Child of the Puddles" },
- { 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: "Cunning Stance", foundryName: "Devrin's Cunning Stance" },
- { pbName: "Constructed (Android)", foundryName: "Constructed" },
- { pbName: "Dazzling Diversion", foundryName: "Devrin's Dazzling Diversion" },
- { 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: "Enhanced Fire", foundryName: "Artokus's Fire" },
- { 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: "Green Watcher", foundryName: "Greenwatcher" },
- { pbName: "Green Watch Initiate", foundryName: "Greenwatch Initiate" },
- { pbName: "Green Watch Veteran", foundryName: "Greenwatch Veteran" },
- { 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: "Lumberjack", foundryName: "Lumber Consortium Laborer" },
- { 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: "Mystic", foundryName: "Nexian Mystic" },
- { pbName: "Paladin [Lawful Good]", foundryName: "Paladin" },
- { pbName: "Parry", foundryName: "Aldori Parry" },
- { pbName: "Polymath", foundryName: "Polymath Muse" },
- { pbName: "Precise Debilitation", foundryName: "Precise Debilitations" },
- { pbName: "Prodigy", foundryName: "Merabite Prodigy" },
- { pbName: "Quick Climber", foundryName: "Quick Climb" },
- { pbName: "Raider", foundryName: "Ulfen Raider" },
- { 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: "Shamanic Adherent", foundryName: "Rivethun Adherent" },
- { pbName: "Shamanic Disciple", foundryName: "Rivethun Disciple" },
- { pbName: "Shamanic Spiritual Attunement", foundryName: "Rivethun Spiritual Attunement" },
- { pbName: "Skysage Dedication", foundryName: "Oatia Skysage Dedication" },
- { 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" },
- // dragons
- { 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" },
- // sizes for fleshwarp
- { pbName: "Medium", foundryName: "med" },
- { pbName: "Small", foundryName: "sm" },
- // Cleric +
- { pbName: "Decree of the Warsworn Ecstacy", foundryName: "Decree of Warsworn Ecstacy" },
- { pbName: "Decree of Warsworn Ecstacy", foundryName: "Decree of the Warsworn Ecstacy" },
-
- // remaster
- { pbName: "Lightning Reflexes", foundryName: "Reflex Expertise" },
- { pbName: "Great Fortitude", foundryName: "Fortitude Expertise" },
- { pbName: "Iron Will", foundryName: "Will Expertise" },
- { pbName: "Alertness", foundryName: "Perception Expertise" },
- { pbName: "Incredible Senses", foundryName: "Perception Legend" },
- { pbName: "Vigilant Senses", foundryName: "Perception Mastery" },
- ];
-
- 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 features_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) {
- const parsed = { pbName, foundryName: match[2], details: match[1] };
- parsed.foundryValue = SKILL_LOOKUP[parsed.foundryName.toLowerCase()];
- result.push(parsed);
- }
- }
- for (const reg of features_SPLITS) {
- const match = pbName.match(reg);
- if (match) {
- result.push({ pbName, foundryName: match[2], details: match[1] });
- }
- }
- for (const reg of features_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 SHARED_IGNORE_LIST = [
- "Draconic Rage", // just handled by effects on Draconic Instinct
- "Mirror Initiate Benefit",
- "Spellstrike Specifics",
- "Unarmored",
- "Simple Weapon Expertise",
- "Spellbook",
- "Titan Mauler", // not needed
- "Energy Emanation", // pathbuilder does not pass through a type for this
- "Imprecise Sense", // this gets picked up and added by granted features
- "Imprecise Scent", // this gets picked up and added by granted features
- "Sanctification", // choose on import
- ];
-
- const IGNORED_FEATS_LIST = [
- // ignore skills listed as feats
- "Acrobatics",
- "Athletics",
- "Deception",
- "Intimidation",
- "Nature",
- "Performance",
- "Society",
- "Survival",
- "Arcana",
- "Crafting",
- "Diplomacy",
- "Medicine",
- "Occultism",
- "Religion",
- "Stealth",
- "Thievery",
-
- // sizes
- // "Medium",
- // "Small",
- ];
-
- const IGNORED_SPECIALS_LIST = [
-
- ];
-
- function IGNORED_FEATS() {
- // const visionFeats = utils.setting("ADD_VISION_FEATS") ? [] : ["Low-Light Vision", "Darkvision"];
- return IGNORED_FEATS_LIST.concat(SHARED_IGNORE_LIST);
- }
-
- function IGNORED_SPECIALS() {
- const visionFeats = src_utils.setting("ADD_VISION_FEATS") ? [] : ["Low-Light Vision", "Darkvision"];
- return IGNORED_SPECIALS_LIST.concat(SHARED_IGNORE_LIST, visionFeats);
- }
-
- function specialOnlyNameLookup(name) {
- for (const [key, value] of Object.entries(SKILL_LOOKUP)) {
- if (key === name.toLowerCase()) {
- return { pbName: name, foundryName: name, foundryValue: value };
- }
- }
- return undefined;
- }
-
-
- function SPECIAL_NAME_ADDITIONS(specials) {
- const newSpecials = [];
-
- for (const special of specials) {
- for (const reg of POSTFIX_PB_SPLIT_AND_KEEP) {
- const match = special.match(reg);
- if (match) {
- newSpecials.push(match[2]);
- }
- }
- }
- return newSpecials;
- }
-
- const NO_AUTO_CHOICE_LIST = [
- // "Elemental Evolution",
- ];
-
- function NO_AUTO_CHOICE() {
- return NO_AUTO_CHOICE_LIST;
- }
-
- ;// CONCATENATED MODULE: ./src/data/spells.js
- const FEAT_SPELLCASTING = [
- { name: "Kitsune Spell Familiarity", showSlotless: false, knownSpells: ["Daze", "Forbidding Ward", "Ghost Sound"], preparePBSpells: true, },
- { name: "Kitsune Spell Expertise", showSlotless: false, knownSpells: ["Confusion", "Death Ward", "Illusory Scene"], preparePBSpells: true, },
- { name: "Kitsune Spell Mysteries", showSlotless: false, knownSpells: ["Bane", "Illusory Object", "Sanctuary"], preparePBSpells: true, },
- { name: "Nagaji Spell Familiarity", showSlotless: false, knownSpells: ["Daze", "Detect Magic", "Mage Hand"], preparePBSpells: true, },
- { name: "Nagaji Spell Expertise", showSlotless: false, knownSpells: ["Blink", "Control Water", "Subconscious Suggestion"], preparePBSpells: true, },
- { name: "Nagaji Spell Mysteries", showSlotless: false, knownSpells: ["Charm", "Fleet Step", "Heal"], preparePBSpells: true, },
- { name: "Rat Magic", showSlotless: false, knownSpells: [], preparePBSpells: true, },
- ];
-
- const REMASTER_NAMES = [
- { pbName: "Animate Dead", foundryName: "Summon Undead" },
- { pbName: "Augment Summoning", foundryName: "Fortify Summoning" },
- { pbName: "Baleful Polymorph", foundryName: "Cursed Metamorphosis" },
- { pbName: "Barkskin", foundryName: "Oaken Resilience" },
- { pbName: "Bind Soul", foundryName: "Seize Soul" },
- { pbName: "Blind Ambition", foundryName: "Ignite Ambition" },
- { pbName: "Blink", foundryName: "Flicker" },
- { pbName: "Burning Hands", foundryName: "Breathe Fire" },
- { pbName: "Calm Emotions", foundryName: "Calm" },
- { pbName: "Charming Words", foundryName: "Charming Push" },
- { pbName: "Chill Touch", foundryName: "Void Warp" },
- { pbName: "Cloudkill", foundryName: "Toxic Cloud" },
- { pbName: "Color Spray", foundryName: "Dizzying Colors" },
- { pbName: "Commune with Nature", foundryName: "Commune" },
- { pbName: "Comprehend Language", foundryName: "Translate" },
- { pbName: "Continual Flame", foundryName: "Everlight" },
- { pbName: "Crushing Despair", foundryName: "Wave of Despair" },
- { pbName: "Dancing Lights", foundryName: "Light" },
- { pbName: "Dimension Door", foundryName: "Translocate" },
- { pbName: "Dimensional Anchor", foundryName: "Planar Seal" },
- { pbName: "Dimensional Lock", foundryName: "Planar Tether" },
- { pbName: "Discern Location", foundryName: "Pinpoint" },
- { pbName: "Disrupt Undead", foundryName: "Vitality Lash" },
- { pbName: "Disrupting Weapons", foundryName: "Infuse Vitality" },
- { pbName: "Endure Elements", foundryName: "Environmental Endurance" },
- { pbName: "Entangle", foundryName: "Entangling Flora" },
- { pbName: "False Life", foundryName: "False Vitality" },
- { pbName: "Feather Fall", foundryName: "Gentle Landing" },
- { pbName: "Feeblemind", foundryName: "Never Mind" },
- { pbName: "Finger of Death", foundryName: "Execute" },
- { pbName: "Flaming Sphere", foundryName: "Floating Flame" },
- { pbName: "Flesh To Stone", foundryName: "Petrify" },
- { pbName: "Freedom of Movement", foundryName: "Unfettered Movement" },
- { pbName: "Gaseous Form", foundryName: "Vapor Form" },
- { pbName: "Gentle Repose", foundryName: "Peaceful Rest" },
- { pbName: "Glibness", foundryName: "Honeyed Words" },
- { pbName: "Glitterdust", foundryName: "Revealing Light" },
- { pbName: "Globe of Invulnerability", foundryName: "Dispelling Globe" },
- { pbName: "Hallucinatory Terrain", foundryName: "Mirage" },
- { pbName: "Hideous Laughter", foundryName: "Laughing Fit" },
- { pbName: "Horrid Wilting", foundryName: "Desiccate" },
- { pbName: "Hypnotic Pattern", foundryName: "Hypnotize" },
- { pbName: "Inspire Competence", foundryName: "Uplifting Overture" },
- { pbName: "Inspire Courage", foundryName: "Courageous Anthem" },
- { pbName: "Inspire Defense", foundryName: "Rallying Anthem" },
- { pbName: "Inspire Heroics", foundryName: "Fortissimo Composition" },
- { pbName: "Know Direction", foundryName: "Know the Way" },
- { pbName: "Legend Lore", foundryName: "Collective Memories" },
- { pbName: "Longstrider", foundryName: "Tailwind" },
- { pbName: "Mage Armor", foundryName: "Mystic Armor" },
- { pbName: "Mage Hand", foundryName: "Telekinetic Hand" },
- { pbName: "Magic Aura", foundryName: "Disguise Magic" },
- { pbName: "Magic Fang", foundryName: "Runic Body" },
- { pbName: "Magic Missile", foundryName: "Force Barrage" },
- { pbName: "Magic Mouth", foundryName: "Embed Message" },
- { pbName: "Magic Weapon", foundryName: "Runic Weapon" },
- { pbName: "Magnificent Mansion", foundryName: "Planar Palace" },
- { pbName: "Maze", foundryName: "Quandary" },
- { pbName: "Meld into Stone", foundryName: "One with Stone" },
- { pbName: "Meteor Swarm", foundryName: "Falling Stars" },
- { pbName: "Mind Blank", foundryName: "Hidden Mind" },
- { pbName: "Misdirection", foundryName: "Disguise Magic" },
- { pbName: "Modify Memory", foundryName: "Rewrite Memory" },
- { pbName: "Neutralize Poison", foundryName: "Cleanse Affliction" },
- { pbName: "Nondetection", foundryName: "Veil of Privacy" },
- { pbName: "Obscuring Mist", foundryName: "Mist" },
- { pbName: "Pass Without Trace", foundryName: "Vanishing Tracks" },
- { pbName: "Passwall", foundryName: "Magic Passage" },
- { pbName: "Phantom Mount", foundryName: "Marvelous Mount" },
- { pbName: "Planar Binding", foundryName: "Planar Servitor" },
- { pbName: "Plane Shift", foundryName: "Interplanar Teleport" },
- { pbName: "Positive Luminance", foundryName: "Vital Luminance" },
- { pbName: "Private Sanctum", foundryName: "Peaceful Bubble" },
- { pbName: "Prying Eye", foundryName: "Scouting Eye" },
- { pbName: "Pulse of The City", foundryName: "Pulse of Civilization" },
- { pbName: "Purify Food And Drink", foundryName: "Cleanse Cuisine" },
- { pbName: "Ray of Enfeeblement", foundryName: "Enfeeble" },
- { pbName: "Remove Curse", foundryName: "Cleanse Affliction" },
- { pbName: "Remove Disease", foundryName: "Cleanse Affliction" },
- { pbName: "Remove Fear", foundryName: "Clear Mind" },
- { pbName: "Remove Paralysis", foundryName: "Sure Footing" },
- { pbName: "Restore Senses", foundryName: "Sound Body" },
- { pbName: "Scorching Ray", foundryName: "Blazing Bolt" },
- { pbName: "Searing Light", foundryName: "Holy Light" },
- { pbName: "See Invisibility", foundryName: "See the Unseen" },
- { pbName: "Shadow Walk", foundryName: "Umbral Journey" },
- { pbName: "Shapechange", foundryName: "Metamorphosis" },
- { pbName: "Shield Other", foundryName: "Share Life" },
- { pbName: "Sound Burst", foundryName: "Noise Blast" },
- { pbName: "Spectral Hand", foundryName: "Ghostly Carrier" },
- { pbName: "Spider Climb", foundryName: "Gecko Grip" },
- { pbName: "Splash of Art", foundryName: "Creative Splash" },
- { pbName: "Stone Tell", foundryName: "Speak with Stones" },
- { pbName: "Stoneskin", foundryName: "Mountain Resilience" },
- { pbName: "Tanglefoot", foundryName: "Tangle Vine" },
- { pbName: "Time Stop", foundryName: "Freeze Time" },
- { pbName: "Tongues", foundryName: "Truespeech" },
- { pbName: "Touch of Idiocy", foundryName: "Stupefy" },
- { pbName: "Tree Shape", foundryName: "One with Plants" },
- { pbName: "Tree Stride", foundryName: "Nature's Pathway" },
- { pbName: "Trueseeing", foundryName: "Truesight" },
- { pbName: "True Strike", foundryName: "Sure Strike" },
- { pbName: "Unseen Servant", foundryName: "Phantasmal Minion" },
- { pbName: "Vampiric Touch", foundryName: "Vampiric Feast" },
- { pbName: "Veil", foundryName: "Illusory Disguise" },
- { pbName: "Vigilant Eye", foundryName: "Rune of Observation" },
- { pbName: "Wail of the Banshee", foundryName: "Wails of the Damned" },
- { pbName: "Wind Walk", foundryName: "Migration" },
- { pbName: "Zone of Truth", foundryName: "Ring of Truth" },
- ];
-
- function spellRename(spellName) {
- if (isNewerVersion(game.version, "5.9.0") && game.modules.get("pf2e-legacy-content")?.active) return spellName;
- const remasterName = REMASTER_NAMES.find((remaster) => remaster.pbName === spellName);
- if (remasterName) {
- return remasterName.foundryName;
- }
- return spellName;
- }
-
- ;// CONCATENATED MODULE: ./src/app/Seasoning.js
-
-
-
-
- /**
- * This class acts as a wrapper around the renaming data,
- * and the changing of names for foundry
- *
- * When Munching we refer to this as Seasoning the data to taste.
- *
- * It's split out just to make it more manageable
- */
- class Seasoning {
-
- // sluggify
- static slug(name) {
- if (!name) return undefined;
- return game.pf2e.system.sluggify(name);
- }
-
- // sluggify with dromedary casing
- static slugD(name) {
- if (!name) return undefined;
- return game.pf2e.system.sluggify(name, { camel: "dromedary" });
- }
-
- static FEAT_RENAME_MAP(name) {
- return FEAT_RENAME_MAP(name);
- }
-
- static EQUIPMENT_RENAME_MAP(name) {
- return EQUIPMENT_RENAME_MAP(name);
- }
-
- static getSpellCastingFeatureAdjustment(name) {
- return FEAT_SPELLCASTING.find((f) => f.name === name);
- }
-
- static getFoundryEquipmentName(pbName) {
- return Seasoning.EQUIPMENT_RENAME_MAP(pbName).find((map) => map.pbName == pbName)?.foundryName ?? pbName;
- }
-
- // static getFoundryFeatureName(pbName) {
- // const match = Seasoning.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
- // return match ?? { pbName, foundryName: pbName, details: undefined };
- // }
-
- static RESTRICTED_EQUIPMENT() {
- return RESTRICTED_EQUIPMENT;
- }
-
- // specials that are handled by Foundry and shouldn't be added
- static IGNORED_FEATS() {
- return IGNORED_FEATS();
- };
-
- static IGNORED_SPECIALS() {
- return IGNORED_SPECIALS();
- }
-
- static IGNORED_EQUIPMENT() {
- return IGNORED_EQUIPMENT;
- };
-
- static IGNORED_EQUIPMENT_DISPLAY(pbName) {
- return IGNORED_EQUIPMENT_DISPLAY(pbName);
- }
-
- static GRANTED_ITEMS_LIST() {
- return GRANTED_ITEMS_LIST;
- }
-
- static getSizeValue(size) {
- switch (size) {
- case 0:
- return "tiny";
- case 1:
- return "sm";
- case 3:
- return "lg";
- default:
- return "med";
- }
- }
-
- static PHYSICAL_ITEM_TYPES = new Set([
- "armor",
- "backpack",
- "book",
- "consumable",
- "equipment",
- "treasure",
- "weapon"
- ]);
-
- static isPhysicalItemType(type) {
- return Seasoning.PHYSICAL_ITEM_TYPES.has(type);
- }
-
- 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 if (pathbuilderFeatType === "Kineticist Feat") { // return as null for now
- return null;
- } else {
- return null;
- }
- }
-
- static getClassAdjustedSpecialNameLowerCase(name, className) {
- return `${name} (${className})`.toLowerCase();
- }
-
- static getDualClassAdjustedSpecialNameLowerCase(name, dualClassName) {
- return `${name} (${dualClassName})`.toLowerCase();
- }
-
- static getAncestryAdjustedSpecialNameLowerCase(name, ancestryName) {
- return `${name} (${ancestryName})`.toLowerCase();
- }
-
- static getHeritageAdjustedSpecialNameLowerCase(name, heritageName) {
- return `${name} (${heritageName})`.toLowerCase();
- }
-
- static getChampionType(alignment) {
- if (alignment == "LG") return "Paladin";
- else if (alignment == "CG") return "Liberator";
- else if (alignment == "NG") return "Redeemer";
- else if (alignment == "LE") return "Tyrant";
- else if (alignment == "CE") return "Antipaladin";
- else if (alignment == "NE") return "Desecrator";
- return "Unknown";
- }
-
-
- }
-
- ;// CONCATENATED MODULE: ./src/app/CompendiumMatcher.js
- /* eslint-disable no-await-in-loop */
-
-
-
-
-
- class CompendiumMatcher {
-
- constructor({ type, mappings = null, indexFields = ["name", "type", "system.slug"] } = {}) {
- this.type = type;
- this.indexFields = indexFields;
- this.packs = {};
-
- const packMappings = mappings !== null
- ? mappings
- : src_utils.setting("USE_CUSTOM_COMPENDIUM_MAPPINGS")
- ? src_utils.setting("CUSTOM_COMPENDIUM_MAPPINGS")
- : constants.CORE_COMPENDIUM_MAPPINGS;
- packMappings[type].forEach((name) => {
- const compendium = game.packs.get(name);
- if (compendium) {
- this.packs[name] = compendium;
- }
- });
-
- this.indexes = {
-
- };
-
- }
-
- async loadCompendiums() {
- for (const [name, compendium] of Object.entries(this.packs)) {
- this.indexes[name] = await compendium.getIndex({ fields: this.indexFields });
- }
- }
-
-
- getFoundryFeatureName(pbName) {
- const match = this.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
- return match ?? { pbName, foundryName: pbName, details: undefined };
- }
-
- getNameMatch(pbName, foundryName) {
- for (const [packName, index] of Object.entries(this.indexes)) {
- const indexMatch = index.find((i) => i.name === foundryName)
- ?? index.find((i) => i.name === pbName);
-
- if (indexMatch) {
- src_logger.debug(`Found name only compendium document for ${pbName} (${foundryName}) in ${packName} with id ${indexMatch._id}`);
- return { i: indexMatch, pack: this.packs[packName] };
- }
- }
- return undefined;
- }
-
- getSlugMatch(pbName, foundryName) {
- for (const [packName, index] of Object.entries(this.indexes)) {
- src_logger.debug(`Checking for compendium documents for ${pbName} (${foundryName}) in ${packName}`, {
- pbName,
- foundryName,
- packName,
- // index,
- // foundrySlug: Seasoning.slug(foundryName),
- // pbSlug: Seasoning.slug(pbName),
- // foundryMatch: index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(foundryName)),
- // pbMatch: index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(pbName)),
- // pbSlugMatch: (null ?? Seasoning.slug("Phase Bolt (Psychic)")) === Seasoning.slug("Phase Bolt (Psychic)"),
- });
- const indexMatch = index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(foundryName))
- ?? index.find((i) => (i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(pbName));
-
- if (indexMatch) {
- src_logger.debug(`Found slug based compendium document for ${pbName} (${foundryName}) in ${packName} with id ${indexMatch._id}`);
- return { i: indexMatch, pack: this.packs[packName] };
- }
- }
- return undefined;
- }
-
- getMatch(pbName, foundryName, forceName = false) {
-
- if (forceName) {
- const nameOnlyMatch = this.getNameMatch(pbName, foundryName);
- if (nameOnlyMatch) return nameOnlyMatch;
- }
-
- const slugMatch = this.getSlugMatch(pbName, foundryName);
- if (slugMatch) return slugMatch;
-
- return undefined;
- }
-
- static checkForFilters(i, filters) {
- for (const [key, value] of Object.entries(filters)) {
- if (getProperty(i, key) !== value) {
- return false;
- }
- }
- return true;
- }
-
- getNameMatchWithFilter(pbName, foundryName, filters = {}) {
- for (const [packName, index] of Object.entries(this.indexes)) {
- src_logger.debug(`Checking for compendium documents for ${pbName} (${foundryName}) in ${packName}`, {
- pbName,
- foundryName,
- filters,
- packName,
- // index,
- });
- const indexMatch = index.find((i) =>
- ((i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(foundryName))
- && CompendiumMatcher.checkForFilters(i, filters))
- ?? index.find((i) =>
- ((i.system.slug ?? Seasoning.slug(i.name)) === Seasoning.slug(pbName)
- && CompendiumMatcher.checkForFilters(i, filters))
- );
-
- if (indexMatch) {
- src_logger.debug(`Found compendium document for ${pbName} (${foundryName}) in ${packName} with id ${indexMatch._id}`);
- return { i: indexMatch, pack: this.packs[packName] };
- }
- }
-
- return undefined;
- }
-
-
- }
-
- ;// CONCATENATED MODULE: ./src/app/CompendiumSelector.js
-
-
-
- class CompendiumSelector extends FormApplication {
- constructor() {
- super();
- this.lookups = src_utils.setting("CUSTOM_COMPENDIUM_MAPPINGS");
- this.packs = game.packs
- .filter((p) => p.metadata.type === "Item")
- .map((p) => {
- return { id: p.metadata.id, label: `${p.metadata.label} (${p.metadata.packageName})` };
- });
- this.currentType = null;
- this.useCustomCompendiums = src_utils.setting("USE_CUSTOM_COMPENDIUM_MAPPINGS");
- }
-
- static get defaultOptions() {
- return mergeObject(super.defaultOptions, {
- id: "pathmuncher-compendium-selector",
- template: `${constants.PATH}/templates/compendium-selector.hbs`,
- width: 722,
- height: 275,
- title: game.i18n.localize(`${constants.FLAG_NAME}.Dialogs.CompendiumSelector.Title`),
- resizable: true,
- classes: ['pathmuncher-compendium-selector'],
- });
- }
-
- getData() {
- const lookups = [];
- for (const key in this.lookups) {
- lookups.push({
- key,
- label: game.i18n.localize(`${constants.FLAG_NAME}.CompendiumGroups.${key}`),
- });
- }
- return {
- lookups,
- title: this.options.title,
- sourceItems: [],
- compendiumItems: [],
- useCustomCompendiums: this.useCustomCompendiums,
- };
- }
-
- async reset() {
- const defaults = constants.GET_DEFAULT_SETTINGS();
- this.lookups = defaults[constants.SETTINGS.CUSTOM_COMPENDIUM_MAPPINGS].default;
- await src_utils.updateSetting("CUSTOM_COMPENDIUM_MAPPINGS", this.lookups);
- this.currentType = null;
- this.render(true);
- }
-
- async enableCustomCompendiums() {
- this.useCustomCompendiums = !this.useCustomCompendiums;
- await src_utils.updateSetting("USE_CUSTOM_COMPENDIUM_MAPPINGS", this.useCustomCompendiums);
- }
-
- filterList(event) {
- const compendiumType = event.srcElement.value;
- const sourceList = document.getElementById("sourceList");
- const compendiumList = document.getElementById("compendiumList");
-
- const sourceOptions = this.packs.filter((p) => !this.lookups[compendiumType].includes(p.id));
- const compendiumOptions = this.packs.filter((p) => this.lookups[compendiumType].includes(p.id));
-
- sourceList.innerHTML = "";
- compendiumList.innerHTML = "";
-
- sourceOptions.forEach((option) => {
- const sourceListItem = document.createElement("option");
- sourceListItem.value = option.id;
- sourceListItem.appendChild(document.createTextNode(option.label));
- sourceList.appendChild(sourceListItem);
- });
-
- compendiumOptions.forEach((option) => {
- const compendiumListItem = document.createElement("option");
- compendiumListItem.value = option.id;
- compendiumListItem.appendChild(document.createTextNode(option.label));
- compendiumList.appendChild(compendiumListItem);
- });
-
- this.currentType = compendiumType;
- }
-
- async updateCompendiums() {
- const compendiumList = document.getElementById("compendiumList");
- const compendiumOptions = Array.from(compendiumList.options);
- const compendiumIds = compendiumOptions.map((option) => {
- return option.value;
- });
-
- this.lookups[this.currentType] = compendiumIds;
-
- src_utils.updateSetting("CUSTOM_COMPENDIUM_MAPPINGS", this.lookups);
- }
-
- async addCompendium() {
- const sourceList = document.getElementById("sourceList");
- const compendiumList = document.getElementById("compendiumList");
- const selectedOptions = Array.from(sourceList.selectedOptions);
-
- selectedOptions.forEach((option) => {
- compendiumList.appendChild(option);
- });
-
- await this.updateCompendiums();
- }
-
- async removeCompendium() {
- const sourceList = document.getElementById("sourceList");
- const compendiumList = document.getElementById("compendiumList");
- const selectedOptions = Array.from(compendiumList.selectedOptions);
-
- selectedOptions.forEach((option) => {
- sourceList.appendChild(option);
- });
- await this.updateCompendiums();
- }
-
- async moveUp() {
- const compendiumList = document.getElementById("compendiumList");
- const selectedOption = compendiumList.selectedOptions[0];
-
- if (selectedOption && selectedOption.previousElementSibling) {
- compendiumList.insertBefore(selectedOption, selectedOption.previousElementSibling);
- }
- await this.updateCompendiums();
- }
-
- async moveDown() {
- const compendiumList = document.getElementById("compendiumList");
- const selectedOption = compendiumList.selectedOptions[0];
-
- if (selectedOption && selectedOption.nextElementSibling) {
- compendiumList.insertBefore(selectedOption.nextElementSibling, selectedOption);
- }
- await this.updateCompendiums();
- }
-
- activateListeners(html) {
- super.activateListeners(html);
-
- document.getElementById("addButton").addEventListener("click", this.addCompendium.bind(this));
- document.getElementById("removeButton").addEventListener("click", this.removeCompendium.bind(this));
- document.getElementById("upButton").addEventListener("click", this.moveUp.bind(this));
- document.getElementById("downButton").addEventListener("click", this.moveDown.bind(this));
- document.getElementById("compSelector").addEventListener("change", this.filterList.bind(this));
- document.getElementById("resetButton").addEventListener("click", this.reset.bind(this));
- document.getElementById("enableCustomCompendiums").addEventListener("change", this.enableCustomCompendiums.bind(this));
- }
- }
-
- ;// CONCATENATED MODULE: ./src/app/Pathmuncher.js
- /* eslint-disable no-await-in-loop */
- /* eslint-disable no-continue */
-
-
-
-
-
-
-
-
- class Pathmuncher {
- FEAT_RENAME_MAP(name) {
- const dynamicItems = [
- { pbName: "Shining Oath", foundryName: `Shining Oath (${Seasoning.getChampionType(this.source.alignment)})` },
- { 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 Seasoning.FEAT_RENAME_MAP(name).concat(dynamicItems);
- }
-
- getFoundryFeatureName(pbName, isSpecial = false) {
- if (isSpecial) {
- const specialMatch = specialOnlyNameLookup(pbName);
- if (specialMatch) return specialMatch;
- }
- const match = this.FEAT_RENAME_MAP(pbName).find((map) => map.pbName == pbName);
- return match ?? { pbName, foundryName: pbName, details: undefined };
- }
-
- constructor(actor, { addFeats = true, addEquipment = true, addSpells = true, adjustBlendedSlots = true,
- addMoney = true, addLores = true, addWeapons = true, addArmor = true, addTreasure = true, addDeity = true,
- addName = true, addClass = true, addBackground = true, addHeritage = true, addAncestry = true,
- statusCallback = null } = {}
- ) {
- this.devMode = game.modules.get("pathmuncher").version === "999.0.0";
- this.actor = actor;
- // note not all these options do anything yet!
- this.options = {
- addTreasure,
- addMoney,
- addFeats,
- addSpells,
- adjustBlendedSlots,
- addEquipment,
- addLores,
- addWeapons,
- addArmor,
- addDeity,
- addName,
- addClass,
- addBackground,
- addHeritage,
- addAncestry,
- };
- 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.promptRules = {};
- this.allFeatureRules = {};
- this.autoAddedFeatureRules = {};
- this.grantItemLookUp = {};
- this.autoFeats = [];
- this.keyAbility = null;
- this.boosts = {
- custom: false,
- class: {},
- background: {},
- ancestry: {},
- };
- this.size = "med";
- 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 = [];
- this.statusCallback = statusCallback;
- this.compendiumMatchers = {};
- const compendiumMappings = src_utils.setting("USE_CUSTOM_COMPENDIUM_MAPPINGS")
- ? src_utils.setting("CUSTOM_COMPENDIUM_MAPPINGS")
- : constants.CORE_COMPENDIUM_MAPPINGS;
- for (const type of Object.keys(compendiumMappings)) {
- this.compendiumMatchers[type] = new CompendiumMatcher({ type });
- }
- }
-
- async #loadCompendiumMatchers() {
- for (const matcher of Object.values(this.compendiumMatchers)) {
- await matcher.loadCompendiums();
- }
- }
-
- #statusUpdate(total, count, type, prefixLabel) {
- if (this.statusCallback) this.statusCallback(total, count, type, prefixLabel);
- }
-
- 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`));
- }
- }
-
- #generateFoundryFeatLocation(document, feature) {
- if (feature.type && feature.level) {
- // const freeArchetypeVariant = game.settings.get("pf2e", "freeArchetypeVariant");
- const location = Seasoning.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: src_utils.allowAncestryParagon(), location, feature });
- // eslint-disable-next-line max-depth
- if (src_utils.allowAncestryParagon() && feature.type === "Ancestry Feat") {
- document.system.location = "ancestry-bonus";
- this.usedLocationsAlternateRules.add(location);
- } else if (src_utils.allowDualClasses() && 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;
- }
- }
-
- #getContainerData(key) {
- return {
- id: key,
- containerName: this.source.equipmentContainers[key].containerName,
- bagOfHolding: this.source.equipmentContainers[key].bagOfHolding,
- backpack: this.source.equipmentContainers[key].backpack,
- };
- }
-
- #nameMapSourceEquipment(e) {
- const name = Seasoning.getFoundryEquipmentName(e[0]);
- const containerKey = Object.keys(this.source.equipmentContainers)
- .find((key) => this.source.equipmentContainers[key].containerName === name);
-
- const container = containerKey ? this.#getContainerData(containerKey) : null;
- const foundryId = foundry.utils.randomID();
-
- if (container) {
- this.source.equipmentContainers[containerKey].foundryId = foundryId;
- }
-
- const item = {
- foundryName: name,
- pbName: e[0],
- originalName: e[0],
- qty: e[1],
- added: false,
- addedId: null,
- addedAutoId: null,
- inContainer: e[2] !== "Invested" ? e[2] : null,
- container,
- foundryId,
- invested: e[2] === "Invested",
- sourceType: "equipment",
- };
- this.parsed.equipment.push(item);
- }
-
- #nameMapSourceEquipmentAddHandwraps(e) {
- const name = Seasoning.getFoundryEquipmentName(e[0]);
- const potencyMatch = e[0].match(/\(\+(\d)[\s)]/i);
- const potency = potencyMatch ? parseInt(potencyMatch[1]) : 0;
- const strikingMatch = e[0].match(/\d( \w*)? (Striking)/i);
- const striking = strikingMatch
- ? Seasoning.slugD(`${(strikingMatch[1] ?? "").trim()}${(strikingMatch[2] ?? "").trim()}`) // `${(strikingMatch[2] ?? "").toLowerCase().trim()}${(strikingMatch[1] ?? "").trim()}`.trim()
- : "";
- const mockE = {
- name: e[0],
- qty: 1,
- prof: "unarmed",
- pot: Number.isInteger(potency) ? potency : 0,
- str: striking,
- mat: null,
- display: e[0],
- runes: [],
- damageType: "B",
- increasedDice: false
- };
- const weapon = mergeObject({
- foundryName: name,
- pbName: mockE.name,
- originalName: mockE.name,
- added: false,
- addedId: null,
- addedAutoId: null,
- sourceType: "weapons",
- }, mockE);
- this.parsed.weapons.push(weapon);
- }
-
- #nameMap() {
- let iRank = 0;
- let featRank = 0;
- src_logger.debug("Starting Equipment Rename");
- this.source.equipment
- .filter((e) => e[0] && e[0] !== "undefined")
- .forEach((e) => {
- if (e[0].startsWith("Handwraps of Mighty Blows")) {
- this.#nameMapSourceEquipmentAddHandwraps(e);
- } else {
- this.#nameMapSourceEquipment(e);
- }
- });
- this.source.armor
- .filter((e) => e && e !== "undefined")
- .forEach((e) => {
- const name = Seasoning.getFoundryEquipmentName(e.name);
- const item = mergeObject({
- foundryName: name,
- pbName: e.name,
- originalName: e.name,
- added: false,
- addedId: null,
- addedAutoId: null,
- sourceType: "armor",
- }, e);
- this.parsed.armor.push(item);
- // work around for now
- if (e.name.startsWith("Inventor ")) {
- this.parsed.feats.push({
- name,
- extra: "",
- added: false,
- addedId: null,
- addedAutoId: null,
- type: "Awarded Feat",
- level: 1,
- originalName: e.name,
- rank: 0,
- sourceType: "armor",
- featChoiceRef: null,
- hasChildren: null,
- isChild: null,
- isStandard: null,
- parentFeatChoiceRef: null,
- });
- featRank++;
- }
- });
- this.source.weapons
- .filter((e) => e && e !== "undefined")
- .forEach((e) => {
- const name = Seasoning.getFoundryEquipmentName(e.name);
- const item = mergeObject({
- foundryName: name,
- pbName: e.name,
- originalName: e.name,
- added: false,
- addedId: null,
- addedAutoId: null,
- sourceType: "weapons",
- }, e);
- this.parsed.weapons.push(item);
- });
- src_logger.debug("Finished Equipment Rename");
-
- src_logger.debug("Starting Special Rename");
- [].concat(this.source.specials, SPECIAL_NAME_ADDITIONS(this.source.specials))
- .filter((special) =>
- special
- && special !== "undefined"
- && special !== "Not Selected"
- && special !== this.source.heritage
- )
- .forEach((special) => {
- const match = this.getFoundryFeatureName(special); // , true);
- if (!this.#processSpecialData(match.foundryName) && !Seasoning.IGNORED_SPECIALS().includes(match.foundryName)) {
- this.parsed.specials.push({ name: match.foundryName, foundryName: match.foundryName, foundryValue: match.foundryValue, originalName: special, added: false, addedId: null, addedAutoId: null, rank: iRank, sourceType: "specials" });
- iRank++;
- }
- });
- 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: feat[0] === this.source.heritage,
- addedId: null,
- addedAutoId: null,
- type: feat[2],
- level: feat[3],
- originalName: feat[0],
- rank: featRank,
- sourceType: "feats",
- };
- if (feat.length >= 7) {
- data.featChoiceRef = feat[4];
- data.hasChildren = feat[5] === "parentChoice";
- data.isChild = feat[5] === "childChoice";
- data.isStandard = feat[5] === "standardChoice";
- data.parentFeatChoiceRef = feat[6];
- const parentFeatMatch = this.source.feats.find((f) =>
- feat[5] === "childChoice"
- && (data.featChoiceRef.toLowerCase().startsWith(f[0].toLowerCase())
- || (data.parentFeatChoiceRef
- && data.featChoiceRef.replace(data.parentFeatChoiceRef, "").trim().toLowerCase().startsWith(f[0].toLowerCase()))
- )
- );
- data.nameHint = parentFeatMatch?.[0];
- } else {
- // probably an awarded feat
- data.featChoiceRef = null;
- data.hasChildren = null;
- data.isChild = null;
- data.isStandard = null;
- data.parentFeatChoiceRef = null;
- }
- this.parsed.feats.push(data);
- featRank++;
- });
- src_logger.debug("Finished Feat Rename");
- src_logger.debug("Name remapping results", {
- parsed: this.parsed,
- });
- }
-
- #fixUps() {
- if (this.source.ancestry === "Dwarf" && !this.parsed.feats.some((f) => f.name === "Clan Pistol")) {
- const clanDagger = {
- name: "Clan Dagger",
- originalName: "Clan Dagger",
- added: false,
- addedId: null,
- addedAutoId: null,
- isChoice: true,
- rank: 0,
- sourceType: "specials",
- };
- this.parsed.specials.push(clanDagger);
- }
-
- const match = this.source.background.match(/(Magical Experiment) \((.*)\)$/);
- if (match) {
- this.parsed.specials.push({
- name: match[2],
- originalName: `${this.source.background}`,
- added: false,
- addedId: null,
- addedAutoId: null,
- isChoice: true,
- rank: 0,
- sourceType: "specials",
- });
- this.source.background = match[1];
- }
- }
-
- async #prepare() {
- await this.#loadCompendiumMatchers();
- this.#nameMap();
- this.#fixUps();
- }
-
- 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);
- }
-
- // eslint-disable-next-line no-unused-vars
- async #addDualClass(klass) {
- if (!src_utils.allowDualClasses()) {
- if (this.source.dualClass && this.source.dualClass !== "") {
- src_logger.warn(`Imported character is dual class. Pathmuncher does not support dual class characters, please check the system macros`, {
- class: this.source.class,
- dualClass: this.source.dualClass,
- });
- ui.notifications.warn(`Imported character is dual class. Pathmuncher does not support dual class characters, please check the system macros`);
- }
- 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;
- }
- src_logger.info("Not processing dual class");
-
- // // find the dual class
- // const foundryName = this.getFoundryFeatureName(this.source.dualClass).foundryName;
- // const indexMatch = this.compendiumMatchers["classes"].getMatch(this.source.dualClass, foundryName);
-
- // if (!indexMatch) return;
- // const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- // const dualClass = doc.toObject();
-
- // 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(type, name, target) {
- src_logger.debug(`Checking for compendium documents for ${name} (${target}) in compendiums for ${type}`);
- const foundryName = this.getFoundryFeatureName(name).foundryName;
- const indexMatch = this.compendiumMatchers[type].getMatch(name, foundryName);
-
- if (indexMatch) {
- const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- const itemData = doc.toObject();
- if (name.includes("(")) {
- const extra = name.split(")")[0].split("(").pop();
- this.parsed.specials.push({ name: doc.name, originalName: name, added: true, extra, rank: 99 });
- }
- if (target === "class") {
- itemData.system.keyAbility.selected = this.keyAbility;
- await this.#addDualClass(itemData);
- }
- itemData._id = foundry.utils.randomID();
- // this.#generateGrantItemData(itemData);
- this.result[target].push(itemData);
- await this.#addGrantedItems(itemData, { applyFeatLocation: target !== "class" });
- 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"
- // }
-
- #slugNameMatch(f, slug) {
- return slug === Seasoning.slug(f.name)
- || slug === Seasoning.slug(f.foundryValue)
- || slug === Seasoning.slug(Seasoning.getClassAdjustedSpecialNameLowerCase(f.name, this.source.class))
- || slug === Seasoning.slug(Seasoning.getAncestryAdjustedSpecialNameLowerCase(f.name, this.source.ancestry))
- || slug === Seasoning.slug(Seasoning.getHeritageAdjustedSpecialNameLowerCase(f.name, this.source.heritage))
- || slug === Seasoning.slug(f.originalName)
- || slug === Seasoning.slug(Seasoning.getClassAdjustedSpecialNameLowerCase(f.originalName, this.source.class))
- || slug
- === Seasoning.slug(Seasoning.getAncestryAdjustedSpecialNameLowerCase(f.originalName, this.source.ancestry))
- || slug
- === Seasoning.slug(Seasoning.getHeritageAdjustedSpecialNameLowerCase(f.originalName, this.source.heritage))
- || (src_utils.allowDualClasses()
- && (slug
- === Seasoning.slug(Seasoning.getDualClassAdjustedSpecialNameLowerCase(f.name, this.source.dualClass))
- || slug
- === Seasoning.slug(
- Seasoning.getDualClassAdjustedSpecialNameLowerCase(f.originalName, this.source.dualClass)
- )));
- }
-
- #parsedFeatureMatch(type, document, slug, { ignoreAdded, isChoiceMatch = false, featType = null } = {}) {
- if (type === "feats" && document) {
- const hintMatch = this.parsed[type].find((f) =>
- (!ignoreAdded || (ignoreAdded && !f.added))
- && f.isChild
- && f.nameHint
- && Seasoning.slug(document.name) === Seasoning.slug(f.nameHint)
- && this.#slugNameMatch(f, slug)
- );
- if (hintMatch) {
- hintMatch.rank = -10;
- return hintMatch;
- }
- }
- // console.warn(`Trying to find ${slug} in ${type}, ignoreAdded? ${ignoreAdded}`, this.parsed[type]);
- const parsedMatch = this.parsed[type].find((f) =>
- (!ignoreAdded || (ignoreAdded && !f.added))
- && (
- featType === null
- || f.type === featType
- )
- && !f.isChoice
- && this.#slugNameMatch(f, slug)
- );
- if (parsedMatch || !document) return parsedMatch;
-
- const extraMatch = this.parsed[type].find((f) =>
- // (!ignoreAdded || (ignoreAdded && !f.added))
- f.extra
- && f.added
- && !f.isChoice
- && Seasoning.slug(f.name) === (document.system.slug ?? Seasoning.slug(document.name))
- && Seasoning.slug(f.extra) === slug
- );
- if (extraMatch) return extraMatch;
-
- if (isChoiceMatch) {
- // console.warn("Specials check", {
- // document,
- // type,
- // slug,
- // });
- const choiceMatch = this.parsed[type].find((f) => f.isChoice && !f.added && Seasoning.slug(f.name) === slug);
- return choiceMatch;
- }
- return undefined;
- }
-
- #generatedResultMatch(type, slug) {
- const featMatch = this.result[type].find((f) => slug === f.system.slug);
- return featMatch;
- }
-
- #findAllFeatureMatch(document, slug, { ignoreAdded, isChoiceMatch = false, featType = null } = {}) {
- // console.warn("Finding all feature matches", { document, slug, ignoreAdded, isChoiceMatch, featType });
- const featMatch = this.#parsedFeatureMatch("feats", document, slug, { ignoreAdded, featType });
- if (featMatch) return featMatch;
- const specialMatch = this.#parsedFeatureMatch("specials", document, slug, { ignoreAdded, isChoiceMatch });
- 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, { itemGrantName = null, originType = null, applyFeatLocation = false } = {}) {
- src_logger.debug(`Adding granted item flags to ${document.name} (parent ${parent.name}) with originType "${originType}", and will applyFeatLocation? ${applyFeatLocation}`);
- if (itemGrantName) {
- const camelCase = Seasoning.slugD(itemGrantName ?? document.system.slug ?? document.name);
- 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);
- this.result.feats.push(document);
- const matchOptions = { ignoreAdded: true, featType: originType };
- const featureMatch
- = this.#findAllFeatureMatch(document, document.system.slug ?? Seasoning.slug(document.name), matchOptions)
- ?? (document.name.includes("(")
- ? this.#findAllFeatureMatch(document, Seasoning.slug(document.name.split("(")[0].trim()), matchOptions)
- : undefined);
-
- if (featureMatch) {
- src_logger.debug(`Found feature match for ${document.name}`, { featureMatch });
- const existingMatch = false;
- // featureMatch.sourceType
- // ? this.parsed[featureMatch.sourceType].some((f) => f.addedId === document._id)
- // : false;
- if (this.devMode && existingMatch) {
- src_logger.warn(`create Granted Item Existing match for ${document.name}`, { featureMatch, existingMatch, document });
- }
- // console.warn(`Match for ${document.name} createGrantedItem`, { featureMatch, existingMatch, document });
- if (hasProperty(featureMatch, "added") && !existingMatch) {
- featureMatch.added = true;
- featureMatch.addedId = document._id;
- if (applyFeatLocation) 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 }
- );
- }
-
-
- static #getLowestChoiceRank(choices) {
- return choices.reduce((p, c) => {
- return p.rank > c.rank ? c : p;
- });
- }
-
- async #featureChoiceMatch(document, choices, ignoreAdded, adjustName, choiceHint = null) {
- const matches = [];
- for (const choice of choices) {
- const doc = adjustName ? game.i18n.localize(choice.label) : await fromUuid(choice.value);
- if (!doc) continue;
- const slug = adjustName
- ? Seasoning.slug(doc)
- : doc.system.slug === null
- ? Seasoning.slug(doc.name)
- : doc.system.slug;
- const featMatch = this.#findAllFeatureMatch(document, slug, { ignoreAdded, isChoiceMatch: false });
- if (featMatch) {
- matches.push({
- slug,
- rank: featMatch.rank,
- choice,
- featMatch,
- });
- }
- }
- if (matches.length > 0) {
- if (choiceHint) {
- const hintMatch = matches.find((m) => m.slug === Seasoning.slug(choiceHint));
- if (hintMatch) return hintMatch;
- }
- if (this.devMode) src_logger.warn(`MATCHES`, matches);
- const match = Pathmuncher.#getLowestChoiceRank(matches);
- const featMatch = this.#findAllFeatureMatch(document, match.slug, { ignoreAdded });
- const existingMatch = false;
- // featMatch.sourceType
- // ? this.parsed[featMatch.sourceType].some((f) => f.addedId === document._id)
- // : false;
- if (this.devMode && existingMatch) {
- src_logger.warn(`Feature Choice Existing match for ${document.name}`, { featMatch, existingMatch, document });
- }
- // console.warn(`Match for ${document.name} featureChoiceMatch`, { match, featMatch, existingMatch, document });
- if (adjustName && hasProperty(featMatch, "added") && !existingMatch) {
- featMatch.added = true;
- featMatch.addedId = document._id;
- }
- src_logger.debug("Choices evaluated", { choices, document, featMatch, match, matches });
- return match.choice;
- } else {
- return undefined;
- }
- }
-
- async #featureChoiceMatchNoUUID(document, choices, cleansedChoiceSet) {
- const matches = [];
- for (const choice of choices) {
- const featMatch = this.#findAllFeatureMatch(document, choice.value, { ignoreAdded: true, isChoiceMatch: true });
- if (featMatch) {
- matches.push({
- rank: featMatch.rank,
- choice,
- });
- }
- }
- if (matches.length > 0) {
- const match = Pathmuncher.#getLowestChoiceRank(matches);
- const featMatch = this.#findAllFeatureMatch(document, match.choice.value, { ignoreAdded: true, isChoiceMatch: true });
-
- const existingMatch = false;
- // featMatch.sourceType
- // ? this.parsed[featMatch.sourceType].some((f) => f.addedId === document._id)
- // : false;
-
- if (this.devMode && existingMatch) {
- src_logger.warn(`NoUUID Existing match for ${document.name}`, { featMatch, existingMatch, document });
- }
- // console.warn(`Match for ${document.name} featureChoiceMatchNoUUID`, { match, featMatch, existingMatch, document });
- if (featMatch && !existingMatch) {
- featMatch.added = true;
- featMatch.addedId = document._id;
- match.choice.nouuid = true;
- }
- src_logger.debug("No UUID Choices evaluated", { choices, cleansedChoiceSet, document, featMatch, match, matches });
- return match.choice;
- } else {
- return undefined;
- }
- }
-
- static getFlag(document, ruleSet) {
- return typeof ruleSet.flag === "string" && ruleSet.flag.length > 0
- ? ruleSet.flag.replace(/[^-a-z0-9]/gi, "")
- : Seasoning.slugD(document.system.slug ?? document.system.name ?? document.name);
- }
-
- async #evaluateChoices(document, choiceSet, choiceHint) {
- src_logger.debug(`Evaluating choices for ${document.name}`, { document, choiceSet, choiceHint });
- const tempActor = await this.#generateTempActor([document], false, false, true);
- const cleansedChoiceSet = deepClone(choiceSet);
- try {
- const item = tempActor.getEmbeddedDocument("Item", document._id);
- const choiceSetRules = new game.pf2e.RuleElements.all.ChoiceSet(cleansedChoiceSet, { parent: item });
- const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
- const choices = await choiceSetRules.inflateChoices(rollOptions, []);
-
- src_logger.debug("Starting choice evaluation", {
- document,
- choiceSet,
- item,
- choiceSetRules,
- rollOptions,
- choices,
- });
-
- if (cleansedChoiceSet.choices?.query) {
- const nonFilteredChoices = await choiceSetRules.inflateChoices(rollOptions, [item]);
- const queryResults = await choiceSetRules.queryCompendium(cleansedChoiceSet.choices, rollOptions, [item]);
- src_logger.debug("Query Result", { queryResults, nonFilteredChoices });
- }
-
- src_logger.debug("Evaluating choiceset", cleansedChoiceSet);
- const choiceMatch = await this.#featureChoiceMatch(document, choices, true, cleansedChoiceSet.adjustName, choiceHint);
- src_logger.debug("choiceMatch result", choiceMatch);
- if (choiceMatch) {
- choiceMatch.choiceQueryResults = deepClone(choices);
- return choiceMatch;
- }
-
- if (typeof cleansedChoiceSet.choices === "string" || Array.isArray(choices)) {
- const featureMatch = await this.#featureChoiceMatchNoUUID(document, choices, cleansedChoiceSet);
- if (featureMatch) {
- return featureMatch;
- }
- }
-
- let tempSet = deepClone(choiceSet);
- src_logger.debug(`Starting dynamic selection for ${document.name}`, { document, choiceSet, tempSet, Pathmuncher: this });
- await choiceSetRules.preCreate({ itemSource: item, ruleSource: tempSet, pendingItems: [item], tempItems: [] });
- // console.warn("chociesetdata", {
- // choiceSetRules,
- // selection: choiceSetRules.selection,
- // choiceSet: deepClone(choiceSet),
- // tempSet: deepClone(tempSet),
- // });
- if (tempSet.selection) {
- const lookedUpChoice = choices.find((c) => c.value === tempSet.selection);
- src_logger.debug("lookedUpChoice", lookedUpChoice);
- if (lookedUpChoice) lookedUpChoice.choiceQueryResults = deepClone(choices);
- // set some common lookups here, e.g. deities are often not set!
- if (lookedUpChoice && cleansedChoiceSet.flag === "deity") {
- if (lookedUpChoice.label && lookedUpChoice.label !== "") {
- setProperty(this.result.character, "system.details.deity.value", lookedUpChoice.label);
- await this.#processGenericCompendiumLookup("deities", lookedUpChoice.label, "deity");
- const camelCase = Seasoning.slugD(this.result.deity[0].system.slug);
- setProperty(document, `flags.pf2e.itemGrants.${camelCase}`, {
- id: this.result.deity[0]._id,
- onDelete: "detach",
- });
- setProperty(this.result.deity[0], "flags.pf2e.grantedBy", { id: document._id, onDelete: "cascade" });
- this.autoAddedFeatureIds.add(`${lookedUpChoice.value.split(".").pop()}deity`);
- }
- }
- return lookedUpChoice;
- }
- } 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(document, ruleEntry) {
- const tempActor = await this.#generateTempActor([document], false, false);
- const cleansedRuleEntry = deepClone(ruleEntry);
- try {
- const item = tempActor.getEmbeddedDocument("Item", document._id);
- // console.warn("creating grant item");
- const grantItemRule = new game.pf2e.RuleElements.all.GrantItem(cleansedRuleEntry, { parent: item });
- // console.warn("Begining uuid resovle");
- const uuid = grantItemRule.resolveInjectedProperties(grantItemRule.uuid, { warn: false });
-
- src_logger.debug("uuid selection", {
- document,
- choiceSet: ruleEntry,
- item,
- grantItemRule,
- uuid,
- });
- if (uuid) return uuid;
- } catch (err) {
- src_logger.error("Whoa! Something went major bad wrong during uuid evaluation", {
- err,
- tempActor: tempActor.toObject(),
- document: duplicate(document),
- ruleEntry: duplicate(cleansedRuleEntry),
- });
- throw err;
- } finally {
- await Actor.deleteDocuments([tempActor._id]);
- }
-
- src_logger.debug("Evaluate UUID failed", { choiceSet: cleansedRuleEntry, tempActor, document });
- return undefined;
- }
-
- async #checkRule(document, rule) {
- const tempActor = await this.#generateTempActor([document], true);
- const cleansedRule = deepClone(rule);
- try {
- const item = tempActor.getEmbeddedDocument("Item", document._id);
- const ruleElement = cleansedRule.key === "ChoiceSet"
- ? new game.pf2e.RuleElements.all.ChoiceSet(cleansedRule, { parent: item })
- : new game.pf2e.RuleElements.all.GrantItem(cleansedRule, { parent: item });
- const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
-
- if (rule.predicate) {
- const predicate = ruleElement.resolveInjectedProperties(ruleElement.predicate);
- if (!predicate.test(rollOptions)) return false;
- }
-
- const choices = cleansedRule.key === "ChoiceSet"
- ? await ruleElement.inflateChoices(rollOptions, [item])
- : [ruleElement.resolveValue()];
-
- const isGood = cleansedRule.key === "ChoiceSet"
- ? (await this.#featureChoiceMatch(document, choices, false)) !== undefined
- : ruleElement.test(rollOptions);
-
- src_logger.debug("Checking rule", {
- tempActor,
- cleansedRule,
- item,
- ruleElement,
- rollOptions,
- choices,
- isGood,
- });
- 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]);
- }
- }
-
- async #checkRulePredicate(document, rule) {
- const tempActor = await this.#generateTempActor([document], true);
- const cleansedRule = deepClone(rule);
- try {
- const item = tempActor.getEmbeddedDocument("Item", document._id);
- const ruleElement = cleansedRule.key === "ChoiceSet"
- ? new game.pf2e.RuleElements.all.ChoiceSet(cleansedRule, { parent: item })
- : new game.pf2e.RuleElements.all.GrantItem(cleansedRule, { parent: item });
- const rollOptions = [tempActor.getRollOptions(), item.getRollOptions("item")].flat();
-
- if (rule.predicate) {
- const predicate = ruleElement.resolveInjectedProperties(ruleElement.predicate);
- return predicate.test(rollOptions);
- } else {
- return true;
- }
- } catch (err) {
- src_logger.error("Something has gone most wrong during rule predicate checking", {
- document,
- rule: cleansedRule,
- tempActor,
- });
- throw err;
- } finally {
- await Actor.deleteDocuments([tempActor._id]);
- }
- }
-
- static adjustDocumentName(featureName, label) {
- const localLabel = game.i18n.localize(label);
- if (featureName.trim().toLowerCase() === localLabel.trim().toLowerCase()) return featureName;
- const name = `${featureName} (${localLabel})`;
- const pattern = (() => {
- const escaped = RegExp.escape(localLabel);
- return new RegExp(`\\(${escaped}\\) \\(${escaped}\\)$`);
- })();
- return name.replace(pattern, `(${localLabel})`);
- }
-
- // eslint-disable-next-line complexity, no-unused-vars
- async #addGrantedRules(document, originType = null, choiceHint = null) {
- 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 = [];
- this.allFeatureRules[document._id] = deepClone(document.system.rules);
- this.autoAddedFeatureRules[document._id] = [];
- this.promptRules[document._id] = [];
- let featureRenamed = false;
-
- for (const ruleEntry of document.system.rules) {
- src_logger.debug(`Ping ${document.name} rule key: ${ruleEntry.key}`, ruleEntry);
- if (!["ChoiceSet", "GrantItem"].includes(ruleEntry.key)) {
- // size work around due to Pathbuilder not always adding the right size to json
- if (ruleEntry.key === "CreatureSize") this.size = ruleEntry.value;
- this.autoAddedFeatureRules[document._id].push(ruleEntry);
- rulesToKeep.push(ruleEntry);
- continue;
- }
- if (NO_AUTO_CHOICE().includes(document.name)) {
- src_logger.debug(`Deliberately skipping ${document.name} auto choice detection`);
- rulesToKeep.push(ruleEntry);
- continue;
- }
- src_logger.debug(`Checking ${document.name} rule key: ${ruleEntry.key}`, {
- ruleEntry,
- docRules: deepClone(document.system.rules),
- document: deepClone(document),
- });
-
- if (ruleEntry.key === "ChoiceSet" && ruleEntry.predicate) {
- src_logger.debug(`Checking for predicates`, {
- ruleEntry,
- document,
- });
- const testResult = await this.#checkRulePredicate(duplicate(document), ruleEntry);
- if (!testResult) {
- const data = { document, ruleEntry, testResult };
- src_logger.debug(
- `The test failed for ${document.name} rule key: ${ruleEntry.key} (This is probably not a problem).`,
- data
- );
- rulesToKeep.push(ruleEntry);
- continue;
- }
- }
-
- const choice = ruleEntry.key === "ChoiceSet" ? await this.#evaluateChoices(document, ruleEntry, choiceHint) : undefined;
- const uuid = ruleEntry.key === "GrantItem" ? await this.#resolveInjectedUuid(document, ruleEntry) : choice?.value;
-
- if (choice?.choiceQueryResults) {
- ruleEntry.choiceQueryResults = choice.choiceQueryResults;
- }
-
- const flagName = Pathmuncher.getFlag(document, ruleEntry);
- // if (flagName && choice?.value && !hasProperty(document, `flags.pf2e.rulesSelections.${flagName}`)) {
- // setProperty(document, `flags.pf2e.rulesSelections.${flagName}`, choice.value);
- // }
-
- src_logger.debug(`UUID for ${document.name}: "${uuid}"`, { document, ruleEntry, choice, uuid });
- const ruleFeature = uuid && typeof uuid === "string" ? await fromUuid(uuid) : undefined;
- // console.warn("ruleFeature", ruleFeature);
- if (ruleFeature) {
- const featureDoc = ruleFeature.toObject();
- featureDoc._id = foundry.utils.randomID();
- if (featureDoc.system.rules) this.allFeatureRules[featureDoc._id] = deepClone(featureDoc.system.rules);
- setProperty(featureDoc, "flags.pathmuncher.origin.uuid", uuid);
- src_logger.debug(`Found rule feature ${featureDoc.name} for ${document.name} for`, ruleEntry);
-
- if (choice) {
- ruleEntry.selection = choice.value;
- setProperty(document, `flags.pf2e.rulesSelections.${flagName}`, choice.value);
- }
-
- if (src_utils.isString(ruleEntry.rollOption)) {
- ruleEntry.rollOption = `${ruleEntry.rollOption}:${flagName}`;
- }
-
- if (ruleEntry.predicate && ruleEntry.key === "GrantItem") {
- src_logger.debug(`Checking for grantitem predicates`, {
- ruleEntry,
- document,
- featureDoc,
- });
- const testResult = await this.#checkRule(featureDoc, ruleEntry);
- if (!testResult) {
- const data = { document, ruleEntry, featureDoc, testResult };
- src_logger.debug(
- `The test failed for ${document.name} rule key: ${ruleEntry.key} (This is probably not a problem).`,
- data
- );
- rulesToKeep.push(ruleEntry);
- // this.autoAddedFeatureRules[document._id].push(ruleEntry);
- continue;
- } else {
- src_logger.debug(`The test passed for ${document.name} rule key: ${ruleEntry.key}`, ruleEntry);
- // this.autoAddedFeatureRules[document._id].push(ruleEntry);
- // eslint-disable-next-line max-depth
- // if (!ruleEntry.flag) ruleEntry.flag = Seasoning.slugD(document.name);
- ruleEntry.pathmuncherImport = true;
- rulesToKeep.push(ruleEntry);
- }
- }
-
- // setProperty(ruleEntry, `preselectChoices.${ruleEntry.flag}`, ruleEntry.selection ?? ruleEntry.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);
- // this.autoAddedFeatureRules[document._id].push(ruleEntry);
- // rulesToKeep.push(ruleEntry);
- if (ruleEntry.key === "GrantItem" && ruleEntry.flag) {
- this.autoAddedFeatureRules[document._id].push(ruleEntry);
- rulesToKeep.push(ruleEntry);
- }
- continue;
- } else {
- src_logger.debug(`Feature ${featureDoc.name} not found for ${document.name}, adding (${ruleFeature.id})`, ruleFeature);
- if (ruleEntry.selection || ruleEntry.flag) {
- rulesToKeep.push(ruleEntry);
- }
- this.autoAddedFeatureIds.add(`${ruleFeature.id}${ruleFeature.type}`);
- featureDoc._id = foundry.utils.randomID();
- this.#createGrantedItem(featureDoc, document, { itemGrantName: flagName, applyFeatLocation: false });
- if (hasProperty(featureDoc, "system.rules")) await this.#addGrantedRules(featureDoc);
- }
- } else if (getProperty(choice, "nouuid")) {
- src_logger.debug("Parsed no id rule", { choice, uuid, ruleEntry });
- if (!ruleEntry.flag) ruleEntry.flag = Seasoning.slugD(document.name);
- ruleEntry.selection = choice.value;
- if (choice.label) document.name = `${document.name} (${choice.label})`;
- rulesToKeep.push(ruleEntry);
- } else if (choice && uuid && !hasProperty(ruleEntry, "selection")) {
- src_logger.debug("Parsed odd choice rule", { choice, uuid, ruleEntry });
- // if (!ruleEntry.flag) ruleEntry.flag = Seasoning.slugD(document.name);
- ruleEntry.selection = choice.value;
- if (
- ((!ruleEntry.adjustName && choice.label && typeof uuid === "object")
- || (!choice.adjustName && choice.label))
- && !featureRenamed
- ) {
- document.name = Pathmuncher.adjustDocumentName(document.name, choice.label);
- featureRenamed = true;
- }
- rulesToKeep.push(ruleEntry);
- } else {
- src_logger.debug(`Final rule fallback for ${document.name}`, ruleEntry);
- const data = {
- uuid: ruleEntry.uuid,
- document,
- ruleEntry,
- choice,
- };
- if (
- ruleEntry.key === "GrantItem"
- && (ruleEntry.flag || ruleEntry.selection || ruleEntry.uuid.startsWith("Compendium"))
- ) {
- rulesToKeep.push(ruleEntry);
- } else if (ruleEntry.key === "ChoiceSet" && !hasProperty(ruleEntry, "flag")) {
- src_logger.debug("Prompting user for choices", ruleEntry);
- this.promptRules[document._id].push(ruleEntry);
- rulesToKeep.push(ruleEntry);
- } else if (ruleEntry.key === "ChoiceSet" && !choice && !uuid) {
- src_logger.warn("Unable to determine choice asking", data);
- rulesToKeep.push(ruleEntry);
- this.promptRules[document._id].push(ruleEntry);
- }
- src_logger.warn("Unable to determine granted rule feature, needs better parser", data);
- }
- if (ruleEntry.adjustName && choice?.label && !featureRenamed) {
- document.name = Pathmuncher.adjustDocumentName(document.name, choice.label);
- }
- this.autoAddedFeatureRules[document._id].push(ruleEntry);
-
- src_logger.debug(`End result for ${document.name} for a ${ruleEntry.key}`, {
- document: deepClone(document),
- rulesToKeep: deepClone(rulesToKeep),
- ruleEntry: deepClone(ruleEntry),
- choice: deepClone(choice),
- uuid: deepClone(uuid),
- });
- }
- // eslint-disable-next-line require-atomic-updates
- document.system.rules = rulesToKeep;
-
- src_logger.debug(`Final status for ${document.name}`, {
- document: deepClone(document),
- rulesToKeep: deepClone(rulesToKeep),
- });
- }
-
- async #addGrantedItems(document, { originType = null, applyFeatLocation = false, choiceHint = null } = {}) {
- const immediateDiveAdd = src_utils.setting("USE_IMMEDIATE_DEEP_DIVE");
- const subRuleDocuments = [];
- 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).sort(([, a], [, b]) => a.level - b.level)) {
- src_logger.debug(`Checking ${document.name} granted item ${grantedItemFeature.name}, level(${grantedItemFeature.level}) 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, { originType, applyFeatLocation });
- if (hasProperty(featureDoc, "system.rules")) {
- src_logger.debug(`Processing granted rules for granted item document ${featureDoc.name}`, duplicate(featureDoc));
- if (immediateDiveAdd) {
- await this.#addGrantedItems(featureDoc, { originType, applyFeatLocation });
- } else {
- subRuleDocuments.push(featureDoc);
- }
- }
- }
- // eslint-disable-next-line require-atomic-updates
- document.system.items = failedFeatureItems;
-
- if (!immediateDiveAdd) {
- for (const subRuleDocument of subRuleDocuments) {
- src_logger.debug(
- `Processing granted rules for granted item document ${subRuleDocument.name}`,
- duplicate(subRuleDocument)
- );
- await this.#addGrantedItems(subRuleDocument, { originType, applyFeatLocation, choiceHint });
- }
- }
- }
-
- if (hasProperty(document, "system.rules")) {
- src_logger.debug(`Processing granted rules for core document ${document.name}`, duplicate(document));
- await this.#addGrantedRules(document, originType, choiceHint);
- }
- }
-
- #determineAbilityBoosts() {
- const breakdown = getProperty(this.source, "abilities.breakdown");
- const useCustomStats
- = breakdown
- && breakdown.ancestryFree.length === 0
- && breakdown.ancestryBoosts.length === 0
- && breakdown.ancestryFlaws.length === 0
- && breakdown.backgroundBoosts.length === 0
- && breakdown.classBoosts.length === 0;
- if (breakdown && !useCustomStats) {
- this.boosts.custom = false;
- const classBoostMap = {};
- for (const [key, boosts] of Object.entries(this.source.abilities.breakdown.mapLevelledBoosts)) {
- if (key <= this.source.level) {
- classBoostMap[key] = boosts.map((ability) => ability.toLowerCase());
- }
- }
- setProperty(this.result.character, "system.build.attributes.boosts", classBoostMap);
- this.boosts.class = classBoostMap;
-
- // ancestry
- } else {
- this.boosts.custom = true;
- ["str", "dex", "con", "int", "wis", "cha"].forEach((key) => {
- const mod = Math.min(Math.max(Math.trunc((this.source.abilities[key] - 10) / 2), -5), 10) || 0;
- setProperty(this.result.character, `system.abilities.${key}.mod`, mod);
- });
- }
-
- if (breakdown?.classBoosts.length > 0) {
- this.keyAbility = breakdown.classBoosts[0].toLowerCase();
- } else {
- this.keyAbility = this.source.keyability;
- }
- setProperty(this.result.character, "system.details.keyability.value", this.keyAbility);
- }
-
- #generateBackgroundAbilityBoosts() {
- if (!this.result.background[0]) return;
- const breakdown = getProperty(this.source, "abilities.breakdown");
- for (const boost of breakdown.backgroundBoosts) {
- for (const [key, boostSet] of Object.entries(this.result.background[0].system.boosts)) {
- if (this.result.background[0].system.boosts[key].selected) continue;
- if (boostSet.value.includes(boost.toLowerCase())) {
- this.result.background[0].system.boosts[key].selected = boost.toLowerCase();
- break;
- }
- }
- }
- }
-
- #generateAncestryAbilityBoosts() {
- if (!this.result.ancestry[0]) return;
- const breakdown = getProperty(this.source, "abilities.breakdown");
- const boosts = [];
- breakdown.ancestryBoosts.concat(breakdown.ancestryFree).forEach((boost) => {
- for (const [key, boostSet] of Object.entries(this.result.ancestry[0].system.boosts)) {
- if (this.result.ancestry[0].system.boosts[key].selected) continue;
- if (boostSet.value.includes(boost.toLowerCase())) {
- this.result.ancestry[0].system.boosts[key].selected = boost.toLowerCase();
- boosts.push(boost.toLowerCase());
- break;
- }
- }
- });
- if (breakdown.ancestryBoosts.length === 0) {
- setProperty(this.result.ancestry[0], "system.alternateAncestryBoosts", boosts);
- }
- }
-
- #setAbilityBoosts() {
- if (this.boosts.custom) return;
- this.#generateBackgroundAbilityBoosts();
- this.#generateAncestryAbilityBoosts();
-
- this.result.class[0].system.boosts = this.boosts.class;
- }
-
- static SKILL_LOOKUP = {
- "acrobatics": "acr",
- "arcana": "arc",
- "athletics": "ath",
- "crafting": "cra",
- "deception": "dec",
- "diplomacy": "dip",
- "intimidation": "itm",
- "medicine": "med",
- "nature": "nat",
- "occultism": "occ",
- "performance": "prf",
- "religion": "rel",
- "society": "soc",
- "stealth": "ste",
- "survival": "sur",
- "thievery": "thi",
- };
-
- #setSkills(removeSpecials = false) {
- for (const [key, value] of Object.entries(Pathmuncher.SKILL_LOOKUP)) {
- const calculatedValue = removeSpecials
- && (this.source.specials.some((s) => s.toLowerCase() === key)
- || this.parsed.specials.some((s) => s.name.toLowerCase() === key))
- ? 0
- : this.source.proficiencies[key] / 2;
- setProperty(this.result.character, `system.skills.${value}.rank`, calculatedValue);
- };
- }
-
- #setSaves() {
- ["fortitude", "reflex", "will"].forEach((key) => {
- setProperty(this.result.character, `system.savingThrows.${key}`, this.source.proficiencies[key] / 2);
- });
- }
-
- #setMartials() {
- ["advanced", "heavy", "light", "medium", "unarmored", "martial", "simple", "unarmed"].forEach((key) => {
- setProperty(this.result.character, `system.martial.${key}.rank`, this.source.proficiencies[key] / 2);
- });
- }
-
- 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);
-
- if (this.source.deity !== "Not set") setProperty(this.result.character, "system.details.deity.value", this.source.deity);
- this.size = Seasoning.getSizeValue(this.source.size);
- setProperty(this.result.character, "system.traits.size.value", this.size);
- setProperty(this.result.character, "system.traits.languages.value", this.source.languages.map((l) => l.toLowerCase()));
-
- this.#processSenses();
-
- this.#determineAbilityBoosts();
- this.#setSaves();
- this.#setMartials();
-
- // 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) => {
- const slug = i.system.slug ?? Seasoning.slug(i.name);
- return (
- slug === Seasoning.slug(name)
- || slug === Seasoning.slug(Seasoning.getClassAdjustedSpecialNameLowerCase(name, this.source.class))
- || slug === Seasoning.slug(Seasoning.getAncestryAdjustedSpecialNameLowerCase(name, this.source.ancestry))
- || slug === Seasoning.slug(Seasoning.getHeritageAdjustedSpecialNameLowerCase(name, this.source.heritage))
- || (src_utils.allowDualClasses()
- && slug === Seasoning.slug(Seasoning.getDualClassAdjustedSpecialNameLowerCase(name, this.source.dualClass)))
- );
- });
- if (indexMatch) return indexMatch;
- }
- return undefined;
- }
-
- #findInPackIndexes(type, arrayOfNameMatches) {
- const matcher = this.compendiumMatchers[type];
- for (const [packName, index] of Object.entries(matcher.indexes)) {
- const indexMatch = this.#indexFind(index, arrayOfNameMatches);
- if (indexMatch) return { i: indexMatch, pack: matcher.packs[packName] };
- }
- return undefined;
- }
-
- #sortParsedFeats() {
- // eslint-disable-next-line complexity
- 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;
- }
- } else if (f1.type === "Awarded Feat" && f2.type === "Awarded Feat") {
- return (f1.level ?? 20) - (f2.level ?? 20);
- } else if (f1.type === "Awarded Feat") {
- return 1;
- } else if (f2.type === "Awarded Feat") {
- return -1;
- } else if ((f1.level ?? 20) === (f2.level ?? 20)) {
- const f1Index = constants.FEAT_PRIORITY.indexOf(f1.type);
- const f2Index = constants.FEAT_PRIORITY.indexOf(f2.type);
- if (f1Index > f2Index) {
- return 1;
- } else if (f1Index < f2Index) {
- return -1;
- } else {
- return 0;
- }
- } else {
- return (f1.level ?? 20) - (f2.level ?? 20);
- }
- });
- }
-
- // eslint-disable-next-line complexity
- async #generateFeatItems(type,
- { levelCap = null, typeFilter = null, excludeChild = false, excludeParents = false, excludeStandard = false } = {}
- ) {
- src_logger.debug(`Generate feat items for ${type} with level cap "${levelCap}" and filter "${typeFilter}"`);
-
- for (const featArray of [this.parsed.feats, this.parsed.specials]) {
- for (const pBFeat of featArray) {
- if (pBFeat.added) continue;
- if (levelCap && (pBFeat.level ?? 20) > levelCap) continue;
- if (typeFilter && pBFeat.type !== typeFilter) continue;
- if (excludeChild && pBFeat.isChild === true) continue;
- if (excludeParents && pBFeat.isParent === true) continue;
- if (excludeStandard && pBFeat.isStandard === true) continue;
- src_logger.debug(`Generating feature for ${pBFeat.name}`, pBFeat);
- if (this.devMode) src_logger.error(`Generating feature for ${pBFeat.name}`, { pBFeat, this: this });
-
- const indexMatch = this.#findInPackIndexes(type, [pBFeat.name, pBFeat.originalName]);
- const displayName = pBFeat.extra ? Pathmuncher.adjustDocumentName(pBFeat.name, pBFeat.extra) : pBFeat.name;
- if (!indexMatch) {
- src_logger.debug(`Unable to match feat ${displayName}`, {
- displayName,
- name: pBFeat.name,
- extra: pBFeat.extra,
- pBFeat,
- type,
- });
- this.check[pBFeat.originalName] = {
- name: displayName,
- type: "feat",
- details: {
- displayName,
- name: pBFeat.name,
- originalName: pBFeat.originalName,
- extra: pBFeat.extra,
- pBFeat,
- type,
- },
- };
- 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, type });
- pBFeat.addedAutoId = `${indexMatch._id}_${indexMatch.type}`;
- continue;
- }
-
- const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- const docData = doc.toObject();
- docData._id = foundry.utils.randomID();
- pBFeat.addedId = docData._id;
- // docData.name = displayName;
-
- this.#generateFoundryFeatLocation(docData, pBFeat);
- this.result.feats.push(docData);
- const options = {
- originType: typeFilter,
- applyFeatLocation: false,
- choiceHint: pBFeat.extra && pBFeat.extra !== "" ? pBFeat.extra : null,
- };
- await this.#addGrantedItems(docData, "feat", options);
- }
- }
- }
-
- async #generateSpecialItems(type) {
- for (const special of this.parsed.specials) {
- if (special.added) continue;
- src_logger.debug("Generating special for", special);
- const indexMatch = this.#findInPackIndexes(type, [special.name, special.originalName]);
- if (!indexMatch) {
- src_logger.debug(`Unable to match special ${special.name}`, { special: special.name, type });
- 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, type });
- special.addedAutoId = `${indexMatch._id}_${indexMatch.type}`;
- continue;
- }
-
- const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- const docData = doc.toObject();
- docData._id = foundry.utils.randomID();
- special.addedId = docData._id;
- this.result.feats.push(docData);
- await this.#addGrantedItems(docData, { applyFeatLocation: true });
- }
- }
-
- #resizeItem(item) {
- if (Seasoning.isPhysicalItemType(item.type)) {
- const resizeItem = item.type !== "treasure" && !["med", "sm"].includes(this.size);
- if (resizeItem) item.system.size = this.size;
- }
- }
-
- async #generateAdventurersPack() {
- const defaultCompendium = game.packs.get("pf2e.equipment-srd");
- const index = await defaultCompendium.getIndex({ fields: ["name", "type", "system.slug"] });
-
-
- const adventurersPack = this.parsed.equipment.find((e) => e.pbName === "Adventurer's Pack");
- if (adventurersPack) {
- const compendiumBackpack = await defaultCompendium.getDocument("3lgwjrFEsQVKzhh7");
- const backpackInstance = compendiumBackpack.toObject();
- adventurersPack.added = true;
- backpackInstance._id = foundry.utils.randomID();
- adventurersPack.addedId = backpackInstance._id;
- 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 defaultCompendium.getDocument(indexMatch._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.quantity = content.qty;
- itemData.system.containerId = backpackInstance?._id;
- this.#resizeItem(itemData);
- this.result.equipment.push(itemData);
- }
- }
- }
-
- async #generateContainers() {
- for (const [key, data] of Object.entries(this.source.equipmentContainers)) {
- if (data.foundryId) continue;
- const name = Seasoning.getFoundryEquipmentName(data.containerName);
- const indexMatch = this.compendiumMatchers["equipment"].getMatch(data.containerName, name);
- const id = foundry.utils.randomID();
- const doc = indexMatch
- ? await indexMatch.pack.getDocument(indexMatch.i._id)
- : await Item.create({ name: data.containerName, type: "backpack" }, { temporary: true });
- const itemData = doc.toObject();
- itemData._id = id;
- this.#resizeItem(itemData);
- this.result["equipment"].push(itemData);
- this.parsed.equipment.push({
- foundryName: name,
- pbName: data.containerName,
- originalName: data.containerName,
- name,
- qty: 1,
- added: true,
- inContainer: undefined,
- container: this.#getContainerData(key),
- foundryId: id,
- });
- }
- }
-
- async #generateEquipmentItems() {
- for (const e of this.parsed.equipment) {
- if (e.pbName === "Adventurer's Pack") continue;
- if (e.added) continue;
- if (Seasoning.IGNORED_EQUIPMENT().includes(e.pbName)) {
- e.added = true;
- e.addedAutoId = "ignored";
- continue;
- }
- src_logger.debug("Generating item for", e);
- const indexMatch = this.compendiumMatchers["equipment"].getMatch(e.pbName, e.foundryName);
- 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 indexMatch.pack.getDocument(indexMatch.i._id);
- if (doc.type != "kit") {
- const itemData = doc.toObject();
- itemData._id = e.foundryId || foundry.utils.randomID();
- itemData.system.quantity = e.qty;
- const type = doc.type === "treasure" ? "treasure" : "equipment";
- if (e.inContainer) {
- const containerMatch = this.parsed.equipment.find((con) => con.container?.id === e.inContainer);
- if (containerMatch) {
- itemData.system.containerId = containerMatch.foundryId;
- itemData.system.equipped.carryType = "stowed";
- }
- }
- if (e.invested) {
- itemData.system.equipped.carryType = "worn";
- itemData.system.equipped.invested = true;
- itemData.system.equipped.inSlot = true;
- itemData.system.equipped.handsHeld = 0;
- }
- this.#resizeItem(itemData);
- this.result[type].push(itemData);
- e.addedId = itemData._id;
- }
- // eslint-disable-next-line require-atomic-updates
- e.added = true;
- }
- }
-
- async #processEquipmentItems() {
- // just in case it's in the equipment, pathbuilder should have translated this to items
- await this.#generateAdventurersPack();
- await this.#generateContainers();
- await this.#generateEquipmentItems();
- }
-
- static RUNE_SCALE = [
- "",
- "Minor",
- "Lesser",
- "Moderate",
- "Greater",
- "Major",
- "Supreme",
- ];
-
- static REINFORCING_DATA = {
- "Minor": {
- value: 1,
- hp: 44,
- },
- "Lesser": {
- value: 2,
- hp: 52,
- },
- "Moderate": {
- value: 3,
- hp: 64,
- },
- "Greater": {
- value: 4,
- hp: 80,
- },
- "Major": {
- value: 5,
- hp: 84,
- },
- "Supreme": {
- value: 6,
- hp: 108,
- },
- };
-
- static POTENCY_SCALE = [
- "",
- "striking",
- "greaterStriking",
- "majorStriking",
- ];
-
- static RESILIENT_SCALE = [
- "",
- "resilient",
- "greaterResilient",
- "majorResilient",
- ];
-
- // eslint-disable-next-line complexity
- static applyRunes(parsedItem, itemData, type) {
- if (itemData.type == "shield") {
- parsedItem.runes.forEach((rune) => {
- if (rune.startsWith("Reinforcing")) {
- const runeScale = rune.split("(").pop().split(")").shift().trim();
- const runeMatch = Pathmuncher.REINFORCING_DATA[runeScale];
- if (runeMatch) {
- itemData.system.runes.reinforcing = runeMatch.value;
- itemData.system.hp.value += runeMatch.hp;
- }
- } else {
- const runeScale = rune.split("(").pop().split(")").shift().trim();
- const runeLevel = Pathmuncher.RUNE_SCALE.indexOf(runeScale);
- const runeType = rune.split("(").shift().toLowerCase().trim();
- if (runeLevel !== -1) {
- itemData.system.runes[runeType] = runeLevel;
- }
- }
- });
- } else if (hasProperty(itemData, "system.runes.potency")) {
- itemData.system.runes.potency = parsedItem.pot;
- if (type === "weapon") {
- const striking = Pathmuncher.POTENCY_SCALE.indexOf(parsedItem.str);
- if (striking !== -1) itemData.system.runes.striking = striking;
- } else if (type === "armor") {
- const resilient = Pathmuncher.RESILIENT_SCALE.indexOf(parsedItem.res);
- if (resilient !== -1) itemData.system.runes.resilient = resilient;
- }
- }
-
- if (type === "armor" && parsedItem.worn
- && ((Number.isInteger(parsedItem.pot) && parsedItem.pot > 0)
- || (parsedItem.res && parsedItem.res !== "")
- )
- ) {
- itemData.system.equipped.invested = true;
- }
-
- if (hasProperty(itemData, "system.runes.property")) {
- parsedItem.runes.forEach((property) => {
- const resistantRegex = /Energy Resistant - (.*)/i;
- const resistantMatch = property.match(resistantRegex);
- const rune = resistantMatch
- ? `${resistantMatch[1]} Resistant`
- : property;
- itemData.system.runes.property.push(Seasoning.slugD(rune));
- });
- }
-
- if (parsedItem.mat) {
- const material = parsedItem.mat.split(" (")[0];
- itemData.system.material.type = Seasoning.slugD(material);
- itemData.system.material.grade = Seasoning.getMaterialGrade(parsedItem.mat);
- }
- }
-
- async #createWeaponItem(data) {
- // { pbName, name, prof, qty, die, display, increasedDice, pot, str, mat, runes, attack, damageBonus, extraDamage, damageType }
- src_logger.debug("Generating weapon for", data);
- const indexMatch = this.compendiumMatchers["equipment"].getMatch(data.pbName, data.foundryName);
- if (!indexMatch) {
- src_logger.error(`Unable to match weapon item ${data.name}`, data);
- this.bad.push({ pbName: data.pbName, type: "weapon", details: { w: data } });
- return null;
- }
-
- const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.quantity = data.qty;
- // because some shields don't have damage dice, but come in as weapons on pathbuilder
- if (itemData.type === "weapon") {
- if (data.die) itemData.system.damage.die = data.die;
- Pathmuncher.applyRunes(data, itemData, "weapon");
- }
-
- if (data.display.startsWith("Large ") || data.increasedDice) {
- itemData.system.size = "lg";
- } else if (data.display && !Seasoning.IGNORED_EQUIPMENT_DISPLAY(data.display)) {
- itemData.name = data.display;
- }
-
- this.#resizeItem(itemData);
- this.result.weapons.push(itemData);
- data.added = true;
- data.addedId = itemData._id;
- return itemData;
- }
-
- async #generateWeaponItems() {
- for (const w of this.parsed.weapons) {
- if (Seasoning.IGNORED_EQUIPMENT().includes(w.pbName)) {
- w.added = true;
- w.addedAutoId = "ignored";
- continue;
- }
- await this.#createWeaponItem(w);
- }
- }
-
- #adjustArmorItem(itemData, parsedArmor) {
- itemData._id = foundry.utils.randomID();
- itemData.system.equipped.value = parsedArmor.worn ?? false;
- if (!Seasoning.RESTRICTED_EQUIPMENT().some((i) => itemData.name.startsWith(i))) {
- itemData.system.equipped.inSlot = parsedArmor.worn ?? false;
- itemData.system.quantity = parsedArmor.qty;
-
- const isShield = parsedArmor.prof === "shield";
- itemData.system.equipped.handsHeld = isShield && parsedArmor.worn ? 1 : 0;
- itemData.system.equipped.carryType = isShield && parsedArmor.worn ? "held" : "worn";
-
- Pathmuncher.applyRunes(parsedArmor, itemData, "armor");
- }
- if (parsedArmor.display) itemData.name = parsedArmor.display;
-
- this.#resizeItem(itemData);
- return itemData;
- }
-
- async #generateArmorItems() {
- for (const a of this.parsed.armor) {
- if (Seasoning.IGNORED_EQUIPMENT().includes(a.pbName)) {
- a.added = true;
- a.addedAutoId = "ignored";
- continue;
- }
-
- src_logger.debug("Generating armor for", a);
- if (Seasoning.GRANTED_ITEMS_LIST().includes(a.pbName)) {
- const existingItem = this.result.armor.find((i) => i.name === a.foundryName);
- if (existingItem) {
- a.added = true;
- a.addedId = existingItem._id;
- src_logger.debug(`Ignoring armor item ${a.pbName} as it has been granted by a feature`);
- continue;
- }
- }
- const indexMatch = this.compendiumMatchers["equipment"].getMatch(a.foundryName, `${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 indexMatch.pack.getDocument(indexMatch.i._id);
- const itemData = this.#adjustArmorItem(doc.toObject(), a);
- this.result.armor.push(itemData);
- a.addedId = itemData._id;
- 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";
-
- // not a focus traditions
- if (caster.magicTradition !== "focus" && ["divine", "occult", "primal", "arcane"].includes(caster.magicTradition)) {
- return caster.magicTradition;
- }
-
- // 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;
- // 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";
- }
-
- #applySpellBlending(spellcastingEntity, caster) {
- if (caster.blendedSpells.length === 0) return;
-
- const remove = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
- const add = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
-
- // find adjustments
- caster.blendedSpells.forEach((slot) => {
- remove[slot.levelFrom]++;
- add[slot.LevelTo]++;
- });
-
- for (let i = 0; i <= 10; i++) {
- const toAdd = this.options.adjustBlendedSlots ? 0 : Math.floor(add[i] / 2);
- const toRemove = this.options.adjustBlendedSlots ? remove[i] : 0;
- const adjustment = 0 - toRemove - toAdd;
- src_logger.debug("Adjusting spells for spell blending", { i, adjustment, add, remove, toAdd, max: spellcastingEntity.slots[`slot${i}`].max });
- spellcastingEntity.slots[`slot${i}`].max += adjustment;
- spellcastingEntity.slots[`slot${i}`].value += adjustment;
- }
- }
-
- #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: {},
- showUnpreparedSpells: { value: true },
- showSlotlessLevels: { value: true },
- };
-
- // apply slot data
- for (let i = 0; i <= 10; i++) {
-
- spellcastingEntity.slots[`slot${i}`] = {
- max: caster.perDay[i],
- prepared: {},
- value: caster.perDay[i],
- };
- }
- // adjust slots for spell blended effects
- this.#applySpellBlending(spellcastingEntity, caster);
-
- const data = {
- _id: foundry.utils.randomID(),
- name,
- type: "spellcastingEntry",
- system: spellcastingEntity,
- };
- this.result.casters.push(data);
- return data;
- }
-
- #generateFocusSpellCaster(proficiency, ability, tradition) {
- const data = {
- _id: foundry.utils.randomID(),
- name: `${src_utils.capitalize(tradition)} Focus Tradition`,
- type: "spellcastingEntry",
- system: {
- ability: {
- value: ability,
- },
- proficiency: {
- value: proficiency / 2,
- },
- spelldc: {
- item: 0,
- },
- tradition: {
- value: tradition,
- },
- prepared: {
- value: "focus",
- flexible: false,
- },
- showUnpreparedSpells: { value: true },
- },
- };
- this.result.casters.push(data);
- return data;
- }
-
- async #loadSpell(spell, casterId, debugData) {
- const spellName = spellRename(spell.split("(")[0].trim());
- src_logger.debug("focus spell details", { spell, spellName, debugData });
-
- const indexMatch = this.compendiumMatchers["spells"].getMatch(spell, spellName, true);
- if (!indexMatch) {
- if (debugData.psychicAmpSpell) return undefined;
- src_logger.error(`Unable to match focus spell ${spell}`, { spell, spellName, debugData });
- this.bad.push({ pbName: spell, type: "spell", details: { originalName: spell, name: spellName, debugData } });
- return undefined;
- }
-
- const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
- itemData.system.location.value = casterId;
-
- return itemData;
- }
-
- // eslint-disable-next-line complexity
- async #processCasterSpells(instance, caster, spellEnhancements, forcePrepare = false) {
- const spellNames = {};
- for (const spellSelection of caster.spells) {
- const level = spellSelection.spellLevel;
- const preparedAtLevel = caster.prepared?.length > 0
- ? (caster.prepared.find((p) => p.spellLevel === level)?.list ?? [])
- : [];
- let preparedValue = 0;
-
- // const preparedMap = preparedAtLevel.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());
-
- for (const [i, spell] of spellSelection.list.entries()) {
- src_logger.debug(`Checking spell at ${i} for level ${level}`, { spell });
- const itemData = await this.#loadSpell(spell, instance._id, {
- spellSelection,
- list: spellSelection.list,
- level,
- instance,
- });
- if (itemData) {
- itemData.system.location.heightenedLevel = level;
- spellNames[spell] = itemData._id;
- this.result.spells.push(itemData);
-
- // if the caster is prepared we don't prepare spells as all known spells come through in JSON
- if (instance.system.prepared.value !== "prepared"
- || spellEnhancements?.preparePBSpells
- || forcePrepare
- || (caster.spellcastingType === "prepared"
- && preparedAtLevel.length === 0 && spellSelection.list.length <= caster.perDay[level])
- ) {
- src_logger.debug(`Preparing spell ${itemData.name} for level ${level}`, { spell });
- // eslint-disable-next-line require-atomic-updates
- instance.system.slots[`slot${level}`].prepared[preparedValue] = { id: itemData._id };
- preparedValue++;
- }
- }
- }
-
- for (const spell of preparedAtLevel) {
- // if (spellNames.includes(spellName)) continue;
- const parsedSpell = getProperty(spellNames, spell);
- const itemData = parsedSpell
- ? this.result.spells.find((s) => s._id === parsedSpell)
- : await this.#loadSpell(spell, instance._id, {
- spellSelection,
- level,
- instance,
- });
- if (itemData) {
- itemData.system.location.heightenedLevel = level;
- if (itemData && !parsedSpell) {
- spellNames[spell] = itemData._id;
- this.result.spells.push(itemData);
- }
-
- src_logger.debug(`Preparing spell ${itemData.name} for level ${level}`, { spellName: spell });
- // eslint-disable-next-line require-atomic-updates
- instance.system.slots[`slot${level}`].prepared[preparedValue] = { id: itemData._id };
- preparedValue++;
- } else {
- src_logger.warn(`Unable to find spell ${spell}`);
- }
- }
-
- if (spellEnhancements?.knownSpells) {
- for (const spell of spellEnhancements.knownSpells) {
- const itemData = await this.#loadSpell(spell, instance._id, {
- spellEnhancements,
- instance,
- });
- if (itemData && !hasProperty(spellNames, itemData.name)) {
- itemData.system.location.heightenedLevel = level;
- spellNames[spell] = itemData._id;
- this.result.spells.push(itemData);
- }
- }
- }
- }
- }
-
- async #processFocusSpells(instance, spells) {
- for (const spell of spells) {
- const itemData = await this.#loadSpell(spell, instance._id, {
- instance,
- spells,
- spell,
- });
- if (itemData) this.result.spells.push(itemData);
- if (spell.endsWith("(Amped)")) {
- const psychicSpell = spell.replace("(Amped)", "(Psychic)");
- const psychicItemData = await this.#loadSpell(psychicSpell, instance._id, {
- instance,
- spells,
- spell: psychicSpell,
- psychicAmpSpell: true,
- });
- if (psychicItemData) {
- this.result.spells.push(psychicItemData);
- }
- }
- }
- }
-
- async #processRituals() {
- if (!this.source.rituals) return;
- const ritualCompendium = new CompendiumMatcher({
- type: "spells",
- indexFields: ["name", "type", "system.slug", "system.ritual"],
- });
- await ritualCompendium.loadCompendiums();
-
- for (const ritual of this.source.rituals) {
- const ritualName = ritual.split("(")[0].trim();
- src_logger.debug("focus spell details", { ritual, spellName: ritualName });
-
- const indexMatch = this.compendiumMatchers["spells"].getNameMatchWithFilter(ritualName, ritualName);
- if (!indexMatch || !hasProperty(indexMatch, "system.ritual")) {
- src_logger.error(`Unable to match ritual spell ${ritual}`, { spell: ritual, spellName: ritualName });
- this.bad.push({ pbName: ritual, type: "spell", details: { originalName: ritual, name: ritualName } });
- continue;
- }
-
- const doc = await indexMatch.pack.getDocument(indexMatch.i._id);
- const itemData = doc.toObject();
- itemData._id = foundry.utils.randomID();
-
- this.result.spells.push(itemData);
- }
- }
-
- async #processSpells() {
- 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 = this.#generateSpellCaster(caster);
- src_logger.debug("Generated caster instance", instance);
- const spellEnhancements = Seasoning.getSpellCastingFeatureAdjustment(caster.name);
- let forcePrepare = false;
- if (hasProperty(spellEnhancements, "showSlotless")) {
- instance.system.showSlotlessLevels.value = getProperty(spellEnhancements, "showSlotless");
- } else if (
- caster.spellcastingType === "prepared"
- && ![this.source.class, this.source.dualClass].includes(caster.name)
- ) {
- const slotToPreparedMatch = caster.spells.every((spellBlock) => {
- const spellCount = spellBlock.list.length;
- const perDay = caster.perDay[spellBlock.spellLevel];
- return perDay === spellCount;
- });
- src_logger.debug(`Setting ${caster.name} show all slots to ${!slotToPreparedMatch}`);
- instance.system.showSlotlessLevels.value = !slotToPreparedMatch;
- forcePrepare = slotToPreparedMatch;
- }
- await this.#processCasterSpells(instance, caster, spellEnhancements, forcePrepare);
- }
-
- for (const tradition of ["occult", "primal", "divine", "arcane"]) {
- const traditionData = getProperty(this.source, `focus.${tradition}`);
- src_logger.debug(`Checking for focus tradition ${tradition}`);
- if (!traditionData) continue;
- for (const ability of ["str", "dex", "con", "int", "wis", "cha"]) {
- const abilityData = getProperty(traditionData, ability);
- src_logger.debug(`Checking for focus tradition ${tradition} with ability ${ability}`);
- if (!abilityData) continue;
- src_logger.debug("Generating focus spellcasting ", { tradition, traditionData, ability });
- const instance = this.#generateFocusSpellCaster(abilityData.proficiency, ability, tradition);
- if (abilityData.focusCantrips && abilityData.focusCantrips.length > 0) {
- await this.#processFocusSpells(instance, abilityData.focusCantrips);
- }
- if (abilityData.focusSpells && abilityData.focusSpells.length > 0) {
- await this.#processFocusSpells(instance, abilityData.focusSpells);
- }
- }
- }
-
- setProperty(this.result.character, "system.resources.focus.max", this.source.focusPoints);
- setProperty(this.result.character, "system.resources.focus.value", this.source.focusPoints);
- }
-
- 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 uuids = [];
-
- for (const formulaSource of this.source.formula) {
- for (const formulaName of formulaSource.known) {
- const indexMatch = this.compendiumMatchers["formulas"].getMatch(formulaName, 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 indexMatch.pack.getDocument(indexMatch.i._id);
- uuids.push({ uuid: doc.uuid });
- }
- }
- setProperty(this.result.character, "system.crafting.formulas", uuids);
- }
-
- async #processFeats() {
- this.#sortParsedFeats();
- // pre pass for standard items
- for (let i = 1; i <= this.result.character.system.details.level.value; i++) {
- await this.#generateFeatItems("feats", { typeFilter: "Ancestry Feat", levelCap: i, excludeChild: true, excludeParents: true });
- await this.#generateFeatItems("feats", { typeFilter: "Skill Feat", levelCap: i, excludeChild: true, excludeParents: true });
- await this.#generateFeatItems("feats", { typeFilter: "Class Feat", levelCap: i, excludeChild: true, excludeParents: true });
- await this.#generateFeatItems("feats", { typeFilter: "General Feat", levelCap: i, excludeChild: true, excludeParents: true });
- }
- await this.#generateFeatItems("ancestryFeatures", { excludeChild: true, excludeParents: true });
- // prepass for non-child items
- for (let i = 1; i <= this.result.character.system.details.level.value; i++) {
- await this.#generateFeatItems("feats", { typeFilter: "Ancestry Feat", levelCap: i, excludeChild: true });
- await this.#generateFeatItems("feats", { typeFilter: "Skill Feat", levelCap: i, excludeChild: true });
- await this.#generateFeatItems("feats", { typeFilter: "Class Feat", levelCap: i, excludeChild: true });
- await this.#generateFeatItems("feats", { typeFilter: "General Feat", levelCap: i, excludeChild: true });
- await this.#generateFeatItems("feats", { typeFilter: "Archetype Feat", levelCap: i, excludeChild: true });
- }
- await this.#generateFeatItems("ancestryFeatures", { excludeChild: true });
-
- this.#setSkills();
- // final pass, include all
- this.#statusUpdate(1, 5, "Feats");
- await this.#generateFeatItems("feats");
- this.#statusUpdate(2, 5, "Feats");
- await this.#generateFeatItems("ancestryFeatures");
- this.#statusUpdate(3, 5, "Feats");
- await this.#generateSpecialItems("ancestryFeatures");
- this.#statusUpdate(4, 5, "Feats");
- await this.#generateSpecialItems("classFeatures");
- this.#statusUpdate(5, 5, "Feats");
- await this.#generateSpecialItems("actions");
- }
-
- async #processEquipment() {
- this.#statusUpdate(1, 4, "Equipment");
- await this.#processEquipmentItems();
- this.#statusUpdate(2, 4, "Weapons");
- await this.#generateWeaponItems();
- this.#statusUpdate(3, 4, "Armor");
- await this.#generateArmorItems();
- this.#statusUpdate(2, 4, "Money");
- await this.#generateMoney();
- }
-
- async #generateTempActor(documents = [], includePassedDocumentsRules = false, includeGrants = false, includeFlagsOnly = false) {
- const actorData = mergeObject({ type: "character" }, this.result.character);
- actorData.name = `Mr Temp (${this.result.character.name})`;
- if (documents.map((d) => d.name.split("(")[0].trim().toLowerCase()).includes("skill training")) {
- delete actorData.system.skills;
- }
-
- const actor = await Actor.create(actorData, { renderSheet: false });
- const currentState = duplicate(this.result);
-
- // console.warn("Initial temp actor", deepClone(actor));
-
- const currentItems = [
- ...currentState.deity,
- ...currentState.ancestry,
- ...currentState.heritage,
- ...currentState.background,
- ...currentState.class,
- ...currentState.lores,
- ...currentState.feats,
- ...currentState.casters,
- // ...currentState.spells,
- // ...currentState.equipment,
- // ...currentState.weapons,
- // ...currentState.armor,
- // ...currentState.treasure,
- // ...currentState.money,
- ];
- for (const doc of documents) {
- if (!currentItems.some((d) => d._id === doc._id)) {
- currentItems.push(deepClone(doc));
- }
- }
- try {
- // if the rule selected is an object, id doesn't take on import
- const ruleUpdates = [];
- for (const i of deepClone(currentItems)) {
- if (!i.system.rules || i.system.rules.length === 0) continue;
- const isPassedDocument = documents.some((d) => d._id === i._id);
- if (isPassedDocument && !includePassedDocumentsRules && !includeFlagsOnly) continue;
-
- const objectSelectionRules = i.system.rules
- .filter((r) => {
- const evaluateRules = ["RollOption", "ChoiceSet"].includes(r.key) && r.selection;
- return !includeFlagsOnly || evaluateRules; // && ["RollOption", "GrantItem", "ChoiceSet", "ActiveEffectLike"].includes(r.key);
- // || (["ChoiceSet"].includes(r.key) && r.selection);
- })
- .map((r) => {
- r.ignored = false;
- return r;
- });
-
- if (objectSelectionRules.length > 0) {
- ruleUpdates.push({
- _id: i._id,
- system: {
- rules: objectSelectionRules,
- },
- });
- }
- }
-
- // console.warn("Rule updates", duplicate(ruleUpdates));
-
- const items = duplicate(currentItems).map((i) => {
- if (i.system.items) i.system.items = [];
- if (i.system.rules) {
- i.system.rules = i.system.rules
- .filter((r) => {
- const isPassedDocument = documents.some((d) => d._id === i._id);
- const isChoiceSetSelection = ["ChoiceSet"].includes(r.key) && r.selection;
- // const choiceSetSelectionObject = isChoiceSetSelection && utils.isObject(r.selection);
- const choiceSetSelectionNotObject = isChoiceSetSelection && !src_utils.isObject(r.selection);
- // const grantRuleWithFlag = includeGrants && ["GrantItem"].includes(r.key) && r.flag;
- const grantRuleWithoutFlag = includeGrants && ["GrantItem"].includes(r.key) && !r.flag;
- // const genericDiscardRule = ["ChoiceSet", "GrantItem", "ActiveEffectLike", "Resistance", "Strike", "AdjustModifier"].includes(r.key);
- const genericDiscardRule = ["ChoiceSet", "GrantItem"].includes(r.key);
- const grantRuleFromItemFlag
- = includeGrants && ["GrantItem"].includes(r.key) && r.uuid.startsWith("{item|flags");
- const rollOptionsRule = ["RollOption"].includes(r.key);
-
- const notPassedDocumentRules
- = !isPassedDocument
- && (choiceSetSelectionNotObject
- // || grantRuleWithFlag
- || grantRuleWithoutFlag
- || !genericDiscardRule);
-
- const passedDocumentRules
- = isPassedDocument
- && includePassedDocumentsRules
- && (isChoiceSetSelection || grantRuleWithoutFlag || grantRuleFromItemFlag || rollOptionsRule);
-
- return notPassedDocumentRules || passedDocumentRules;
- })
- .map((r) => {
- // if choices is a string or an object then we replace with the query string results
- if ((src_utils.isString(r.choices) || src_utils.isObject(r.choices)) && r.choiceQueryResults) {
- r.choices = r.choiceQueryResults;
- }
- r.ignored = false;
- return r;
- });
- }
- return i;
- });
-
- // const items2 = duplicate(currentItems).map((i) => {
- // if (i.system.items) i.system.items = [];
- // if (i.system.rules) i.system.rules = i.system.rules.filter((r) =>
- // (!documents.some((d) => d._id === i._id)
- // && ((["ChoiceSet",].includes(r.key) && r.selection)
- // // || (["GrantItem"].includes(r.key) && r.flag)
- // || !["ChoiceSet", "GrantItem"].includes(r.key)
- // ))
- // || (includePassedDocumentsRules && documents.some((d) => d._id === i._id) && ["ChoiceSet",].includes(r.key) && r.selection)
- // ).map((r) => {
- // if ((typeof r.choices === 'string' || r.choices instanceof String)
- // || (typeof r.choices === 'object' && !Array.isArray(r.choices) && r.choices !== null && r.choiceQueryResults)
- // ) {
- // r.choices = r.choiceQueryResults;
- // }
- // r.ignored = false;
- // return r;
- // });
- // return i;
- // });
-
- // console.warn("temp items", {
- // documents: deepClone(currentItems),
- // items: deepClone(items),
- // // items2: deepClone(items2),
- // // diff: diffObject(items, items2),
- // includePassedDocumentsRules,
- // includeGrants,
- // });
- await actor.createEmbeddedDocuments("Item", items, { keepId: true });
- // console.warn("restoring selection rules to temp items", 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),
- },
- });
- }
-
- // console.warn("restoring feature items to temp items", itemUpdates);
- 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;
- await this.#prepare();
- this.#statusUpdate(1, 12, "Character");
- await this.#processCore();
- this.#statusUpdate(2, 12, "Formula");
- await this.#processFormulas();
- this.#statusUpdate(3, 12, "Deity");
- await this.#processGenericCompendiumLookup("deities", this.source.deity, "deity");
-
- this.#statusUpdate(4, 12, "Ancestry");
- await this.#processGenericCompendiumLookup("ancestries", this.source.ancestry, "ancestry");
- this.#statusUpdate(5, 12, "Heritage");
- await this.#processGenericCompendiumLookup("heritages", this.source.heritage, "heritage");
- this.#statusUpdate(6, 12, "Background");
- await this.#processGenericCompendiumLookup("backgrounds", this.source.background, "background");
-
- this.#setSkills(true);
-
- this.#statusUpdate(7, 12, "Class");
- await this.#processGenericCompendiumLookup("classes", this.source.class, "class");
-
- this.#setAbilityBoosts();
-
- this.#statusUpdate(8, 12, "FeatureRec");
- await this.#processFeats();
- this.#statusUpdate(10, 12, "Equipment");
- await this.#processEquipment();
- this.#statusUpdate(11, 12, "Spells");
- await this.#processSpells();
- this.#statusUpdate(11, 12, "Rituals");
- await this.#processRituals();
- this.#statusUpdate(12, 12, "Lores");
- 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 #createAndUpdateItemsWithRuleRestore(items) {
- const ruleUpdates = [];
-
- const newItems = deepClone(items);
-
- for (const item of newItems) {
- if (item.system.rules?.length > 0) {
- ruleUpdates.push({
- _id: item._id,
- system: {
- rules: deepClone(item.system.rules).map((r) => {
- delete r.choiceQueryResults;
- return r;
- }),
- },
- });
- item.system.rules = item.system.rules
- .filter((r) => {
- const excludedKeys = ["ActiveEffectLike", "AdjustModifier", "Resistance", "Strike"].includes(r.key);
- const grantItemWithFlags = ["GrantItem"].includes(r.key) && (hasProperty(r, "flag") || getProperty(r, "pathmuncherImport"));
- const objectSelection = ["ChoiceSet"].includes(r.key) && src_utils.isObject(r.selection);
- return !excludedKeys && !grantItemWithFlags && !objectSelection;
- })
- .map((r) => {
- if (r.key === "ChoiceSet") {
- if ((src_utils.isString(r.choices) || src_utils.isObject(r.choices)) && r.choiceQueryResults) {
- r.choices = r.choiceQueryResults;
- }
- }
- if (r.pathmuncherImport) delete r.pathmuncherImport;
- return r;
- });
- }
- }
-
- src_logger.debug("Creating items", newItems);
- await this.actor.createEmbeddedDocuments("Item", newItems, { keepId: true });
- src_logger.debug("Rule updates", ruleUpdates);
- await this.actor.updateEmbeddedDocuments("Item", ruleUpdates);
- }
-
- async #updateItems(type) {
- src_logger.debug(`Updating ${type}`, this.result[type]);
- await this.actor.updateEmbeddedDocuments("Item", this.result[type]);
- }
-
- async #createActorEmbeddedDocuments() {
- this.#statusUpdate(1, 12, "Character", "Eating");
- if (this.options.addDeity) await this.#createAndUpdateItemsWithRuleRestore(this.result.deity);
- if (this.options.addAncestry) await this.#createAndUpdateItemsWithRuleRestore(this.result.ancestry);
- if (this.options.addHeritage) await this.#createAndUpdateItemsWithRuleRestore(this.result.heritage);
- if (this.options.addBackground) await this.#createAndUpdateItemsWithRuleRestore(this.result.background);
- if (this.options.addClass) await this.#createAndUpdateItemsWithRuleRestore(this.result.class);
- if (this.options.addLores) await this.#createAndUpdateItemsWithRuleRestore(this.result.lores);
-
- const featNums = this.result.feats.length;
- if (this.options.addFeats) {
- for (const [i, feat] of this.result.feats.entries()) {
- // console.warn(`creating ${feat.name}`, feat);
- this.#statusUpdate(i, featNums, "Feats", "Eating");
- await this.#createAndUpdateItemsWithRuleRestore([feat]);
- }
- }
- // if (this.options.addFeats) await this.#createAndUpdateItemsWithRuleRestore(this.result.feats);
- if (this.options.addSpells) {
- this.#statusUpdate(3, 12, "Spells", "Eating");
- await this.#createAndUpdateItemsWithRuleRestore(this.result.casters);
- await this.#createAndUpdateItemsWithRuleRestore(this.result.spells);
- }
- this.#statusUpdate(4, 12, "Equipment", "Eating");
- if (this.options.addEquipment) {
- await this.#createAndUpdateItemsWithRuleRestore(this.result.equipment);
- await this.#updateItems("equipment");
- }
- if (this.options.addWeapons) await this.#createAndUpdateItemsWithRuleRestore(this.result.weapons);
- if (this.options.addArmor) {
- await this.#createAndUpdateItemsWithRuleRestore(this.result.armor);
- await this.#updateItems("armor");
- }
- if (this.options.addTreasure) await this.#createAndUpdateItemsWithRuleRestore(this.result.treasure);
- if (this.options.addMoney) await this.#createAndUpdateItemsWithRuleRestore(this.result.money);
- }
-
- async #restoreEmbeddedRuleLogic() {
- const importedItems = this.actor.items.map((i) => i._id);
- // Loop back over items and add rule and item progression data back in.
- src_logger.debug("Restoring logic", { currentActor: duplicate(this.actor) });
- const itemUpdates = [];
- for (const [key, value] of Object.entries(this.autoAddedFeatureItems)) {
- if (importedItems.includes(key)) {
- itemUpdates.push({
- _id: `${key}`,
- system: {
- items: deepClone(value),
- },
- });
- }
- }
- this.#statusUpdate(1, 12, "Feats", "Clearing");
- src_logger.debug("Restoring granted item logic", itemUpdates);
- await this.actor.updateEmbeddedDocuments("Item", itemUpdates);
-
- await this.actor.update({
- "system.resources.focus": this.result.character.system.resources.focus,
- });
- }
-
- 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;
- }
-
- if (!this.boosts.custom) {
- setProperty(this.result.character, `system.abilities`, null);
- }
-
- 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" && b.pbName !== "").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"
- && this.parsed.feats.some((f) => f.name === b.details.name && !f.added)
- ).map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Feats")}: ${b.details.name}</li>`)
- : [];
- const badSpecials = this.options.addFeats
- ? Object.values(this.check).filter((b) =>
- (b.type === "special")
- && this.parsed.specials.some((f) => f.name === b.details.name && !f.added)
- ).map((b) => `<li>${game.i18n.localize("pathmuncher.Labels.Specials")}: ${b.details.originalName}</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,
- ...badSpecials,
- ...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>`;
- }
-
- src_logger.debug("Bad thing check", {
- badClass,
- badAncestry,
- badHeritage,
- badBackground,
- badDeity,
- badFeats,
- badFeats2,
- badSpecials,
- badEquipment,
- badWeapons,
- badArmor,
- badSpellcasting,
- badSpells,
- badFamiliars,
- badFormulas,
- totalBad,
- count: totalBad.length,
- focusPool: this.result.focusPool,
- warning,
- });
-
- if (totalBad.length > 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 ({ type = "familiar", parent, pathbuilderJson } = {}) {
- this.parent = parent;
- this.pathbuilderJson = pathbuilderJson;
- this.type = type;
-
- this.result = {
- pets: [],
- features: {},
- };
-
- this.bad = {};
- this.folders = {};
- }
-
-
- async ensureFolder(type) {
- const folderName = game.i18n.localize(`${constants.FLAG_NAME}.Folders.${type}`);
- this.folders[type] = await src_utils.getOrCreateFolder(this.parent.folder, "Actor", folderName);
- }
-
- async #existingPetCheck(petName, type) {
- const existingPet = game.actors.find((a) =>
- a.type === type.toLowerCase()
- && a.name === petName
- && a.system.master.id === this.parent._id
- );
-
- if (existingPet) return existingPet.toObject();
-
- const actorData = {
- type: type.toLowerCase(),
- name: petName,
- system: {
- master: {
- id: this.parent._id,
- ability: this.parent.system.details.keyability.value,
- },
- },
- prototypeToken: {
- name: petName,
- },
- 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() {
- const petData = this.type === "familiar" && this.pathbuilderJson.familiars
- ? this.pathbuilderJson.familiars
- : this.pathbuilderJson.pets.filter((p) => this.type === p.type.toLowerCase());
- await this.ensureFolder(src_utils.capitalize(this.type));
- for (const petJson of petData) {
- 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, prefixLabel = "Cooking") {
- const localizedType = game.i18n.localize(`pathmuncher.Labels.${type}`);
- const progressBar = document.getElementById("pathmuncher-status");
- progressBar.style.width = `${Math.trunc((count / total) * 100)}%`;
- progressBar.innerHTML = `<span>${game.i18n.localize(`pathmuncher.Labels.${prefixLabel}`)} (${localizedType})...</span>`;
- }
-
- async _updateObject(event, formData) {
- document.getElementById("pathmuncher-button").disabled = true;
- const pathbuilderId = formData.textBoxBuildID;
- const options = {
- pathbuilderId,
- addMoney: formData.checkBoxMoney,
- addFeats: formData.checkBoxFeats,
- addSpells: formData.checkBoxSpells,
- adjustBlendedSlots: formData.checkBoxBlendedSlots,
- 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,
- statusCallback: PathmuncherImporter._updateProgress.bind(this),
- };
- src_logger.debug("Pathmuncher options", options);
-
- await src_utils.setFlags(this.actor, options);
-
- const statusBar = document.getElementById("pathmuncher-import-progress");
- statusBar.classList.toggle("import-hidden");
-
- 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.trim());
- 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({
- type: "familiar",
- 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,
- PetShop: PetShop,
- CompendiumMatcher: CompendiumMatcher,
- Seasoning: Seasoning,
- CompendiumSelector: CompendiumSelector,
- 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);
- }
-
- game.settings.registerMenu(constants.MODULE_NAME, "selectCustomCompendiums", {
- name: `${constants.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Title`,
- label: `${constants.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Label`,
- hint: `${constants.FLAG_NAME}.Settings.UseCustomCompendiumMappings.Hint`,
- icon: "fas fa-book",
- type: CompendiumSelector,
- restricted: true,
- });
-
- }
-
- ;// CONCATENATED MODULE: ./src/hooks/sheets.js
-
-
-
-
- function registerSheetButton() {
-
- const trustedUsersOnly = src_utils.setting("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> ${constants.MODULE_FULL_NAME}</a>`);
-
- button.click(() => {
- if (game.user.can("ACTOR_CREATE")) {
- const muncher = new PathmuncherImporter(PathmuncherImporter.defaultOptions, data.actor);
- muncher.render(true);
- } else {
- ui.notifications.warn(game.i18n.localize(`${constants.FLAG_NAME}.Notifications.CreateActorPermission`), { permanent: 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
|