{"name":"Detach Scene from Scene Packer instance","type":"script","author":"MRUsyvFBYyl6GZtk","img":"icons/svg/hanging-sign.svg","scope":"global","command":"// This macro will detach a scene from a Scene Packer instance, allowing you to\n// choose a different module to pack the scene against.\n// Use this macro if you accidentally pack a scene against the wrong module.\n\nconst sceneName = 'Your scene name';\ngame.scenes.filter(s => s.name === sceneName).forEach(s => s.setFlag('scene-packer', 'source-module', null))","folder":null,"sort":0,"permission":{"default":0,"MRUsyvFBYyl6GZtk":3},"flags":{"core":{"sourceId":"Macro.CPhN32K7a5EtMlBH"},"scene-packer":{"sourceId":"Macro.CPhN32K7a5EtMlBH"}},"_id":"1nFGsw2WJuSB10FC"}
{"name":"Enable recommended modules","type":"script","author":"0r6mrq3YfoHRSSBu","img":"icons/svg/thrust.svg","scope":"global","command":"/*\n * Enable recommended modules\n */\n(async () => {\n  // Adjust the following recommended modules to those that you recommend enabling in conjunction with your module.\n  const recommendedModules = [\n    {\n      'id': 'scene-packer',\n      'title': \"Scene Packer\",\n      'url': 'https://github.com/League-of-Foundry-Developers/scene-packer',\n    },\n    {\n      'id': 'lib-wrapper',\n      'title': 'libWrapper',\n      'url': 'https://github.com/ruipin/fvtt-lib-wrapper',\n    },\n  ];\n\n  const allModules = Array.from(game.modules.values());\n  const activeModules = allModules.filter(m => m.active);\n  const unavailableModules = recommendedModules.filter(m => !allModules.find(x => x.id === m.id));\n  const availableModules = recommendedModules.filter(m => allModules.find(x => x.id === m.id) && !activeModules.find(x => x.id === m.id));\n  const allRecommendedModulesEnabled = recommendedModules.every(m => activeModules.find(x => x.id === m.id));\n  let availableSelections = 0;\n\n  let content = '';\n\n  if (allRecommendedModulesEnabled) {\n    content += '<p>All recommended modules are already enabled.</p>';\n    content += '<ol>';\n    for (const module of recommendedModules) {\n      content += `<li><a href=\"${module.url}\">${module.title}</a></li>`;\n    }\n    content += '</ol>';\n  } else if (availableModules.length) {\n    content += '<p>Enable the recommended modules:</p>';\n    content += '<ul>';\n    for (const module of recommendedModules) {\n      const available = allModules.find(x => x.id === module.id);\n      if (!available) {\n        continue;\n      }\n\n      const active = activeModules.find(x => x.id === module.id);\n      const missingDependencies = game.modules.get(module.id).data.dependencies.filter(d => !activeModules.find(x => x.id === d.name));\n      if (active && !missingDependencies.length) {\n        continue;\n      }\n\n      availableSelections++;\n      content += `<li><label><input type=\"checkbox\" name=\"${module.id}\" checked> ${module.title}</label>`;\n\n      if (missingDependencies.length) {\n        content += '<ul>';\n        for (const dependency of missingDependencies) {\n          const available = allModules.find(x => x.id === dependency.name);\n          if (!available) {\n            const url = dependency.url || dependency.manifest || `https://foundryvtt.com/packages/${dependency.name}/`;\n            unavailableModules.push({\n              id: dependency.name,\n              title: dependency.name,\n              url,\n            });\n            continue;\n          }\n\n          availableSelections++;\n          content += `<li><label><input type=\"checkbox\" name=\"${dependency.name}\" checked> ${dependency.name}</label></li>`;\n        }\n        content += '</ul>';\n      }\n\n      content += '</li>';\n    }\n    content += '</ul>';\n  }\n  if (unavailableModules.length) {\n    content += '<p>The following modules cannot be enabled as they are not installed:</p>';\n    content += '<ul>';\n    for (const module of unavailableModules) {\n      content += `<li><a href=\"${module.url}\">${module.title}</a></li>`;\n    }\n    content += '</ul>';\n  }\n\n  const buttons = {\n    close: {\n      icon: '<i class=\"fas fa-times\"></i>',\n      label: 'Close',\n      callback: () => {\n      },\n    },\n  };\n\n  if (availableSelections) {\n    buttons.enabled = {\n      icon: '<i class=\"fas fa-check\"></i>',\n      label: 'Enable selected modules',\n      condition: !allRecommendedModulesEnabled,\n      callback: async (html) => {\n        const selectedModules = new Set();\n        html.find('input[type=checkbox]').each((i, x) => {\n          if (x.checked) {\n            selectedModules.add(x.name);\n          }\n        });\n        if (!selectedModules.size) {\n          return;\n        }\n\n        const settings = game.settings.get(\"core\", ModuleManagement.CONFIG_SETTING);\n        for (const selectedModule of selectedModules.values()) {\n          settings[selectedModule] = true;\n        }\n        await game.settings.set(\"core\", ModuleManagement.CONFIG_SETTING, settings);\n      },\n    }\n  }\n  new Dialog({\n    title: 'Recommended Modules',\n    content: content,\n    buttons: buttons,\n    default: 'close',\n  }).render(true);\n})();","folder":null,"sort":0,"permission":{"default":0,"0r6mrq3YfoHRSSBu":3},"flags":{"scene-packer":{"hash":"7188c52504288842f7f6851d5a1c778c2a8ac24c","sourceId":"Macro.M87CX2o870IU7fC9"},"core":{"sourceId":"Macro.M87CX2o870IU7fC9"}},"_id":"1o9vvd8xoOugORpY"}
{"_id":"DMRaZaBX7hr2TNoZ","name":"Show scene performance report","permission":{"default":0,"PeG0c6MgW8W2NzFs":3},"type":"script","flags":{"core":{"sourceId":"Macro.frlsbESCLKNBjrYG"}},"scope":"global","command":"// This macro shows a performance report that details the\n// complexity in a Scene. It can be used as a guide to help\n// determine how a Scene may perform for players.\n\nScenePacker.ShowPerformanceReport();","author":"PeG0c6MgW8W2NzFs","img":"icons/svg/upgrade.svg","actorIds":[]}
{"_id":"IeiuU5QSNgwJygTL","name":"Relink compendium entries","type":"script","author":"PeG0c6MgW8W2NzFs","img":"icons/svg/book.svg","scope":"global","command":"// This macro will let you choose your module and will check\n// each Journal and Item in your module's compendium in an\n// attempt to update the link to the equivalent compendium\n// version.\n//\n// Asks you whether you want to operate in a \"dry run\" mode,\n// where no changes are made. Check the Console for output.\n\nScenePacker.PromptRelinkJournalEntries()","folder":null,"sort":0,"permission":{"default":0,"PeG0c6MgW8W2NzFs":3},"flags":{"core":{"sourceId":"Macro.1vPAJ3r886Km4CSi"}}}
{"_id":"L2BGaj7HBxJHOSkM","name":"Bulk replace asset references","type":"script","author":"MRUsyvFBYyl6GZtk","img":"icons/svg/lightning.svg","scope":"global","command":"/*\n * Bulk replace asset references\n */\n(async () => {\n  Dialog.prompt({\n    title: 'Bulk replace asset references',\n    content:\n      '<p>This tool will search for asset references <strong>within your world</strong> that start with the provided value and replace that portion with the other provided value.</p>' +\n      '<p><label>Find (case sensitive): <input type=\"text\" name=\"find\" placeholder=\"some/original/path/\"></label></p>' +\n      '<p><label>Replace (case sensitive): <input type=\"text\" name=\"replace\" placeholder=\"your/new/path/\"></label></p>' +\n      '<hr><p><label><input type=\"checkbox\"> Save changes?</label></p><p>Leaving this unchecked will operate in a \"dry run\" mode, where changes are only output to the console (F12).</p><hr>',\n    label: 'Bulk replace',\n    callback: async (html) => {\n      const find = html.find('input[name=\"find\"]').val();\n      const replace = html.find('input[name=\"replace\"]').val();\n      const dryRun = !html.find('input[type=\"checkbox\"]')[0].checked;\n      if (!find || !replace) {\n        ui.notifications.warn('Please provide both a find and replace value.', {permanent: true});\n        console.warn('Bulk replace asset references: Please provide both a find and replace value.');\n        return;\n      }\n\n      const documentTypes = CONST.COMPENDIUM_DOCUMENT_TYPES || CONST.COMPENDIUM_ENTITY_TYPES || [];\n      for (const documentType of documentTypes) {\n        const collectionName = CONFIG[documentType]?.documentClass?.collectionName;\n        if (!collectionName) {\n          console.warn(`Bulk replace asset references: No collection name for ${documentType}`);\n          continue;\n        }\n\n        const collection = game[collectionName];\n        if (!collection) {\n          continue;\n        }\n\n        console.groupCollapsed(`Bulk replace asset references: ${collectionName}`);\n        for (const document of collection) {\n          const original = document.toJSON();\n          const replacementData = JSON.stringify(original).replaceAll(`\"${find}`, `\"${replace}`);\n          if (original === replacementData) {\n            continue;\n          }\n\n          const diff = foundry.utils.diffObject(original, JSON.parse(replacementData));\n          if (typeof foundry.utils.isEmpty === 'function' ? foundry.utils.isEmpty(diff) : foundry.utils.isObjectEmpty(diff)) {\n            continue;\n          }\n\n          console.log(`Bulk replace asset references: Updating ${documentType} ${document.id} ${document.name}`);\n          console.table(diff);\n          if (!dryRun) {\n            await document.update(diff);\n          }\n        }\n\n        console.groupEnd();\n      }\n\n      ui.notifications.info('Bulk replace asset references complete. Check the console (F12) for full details.', {permanent: true});\n      console.log('Bulk replace asset references complete.');\n    },\n  });\n})();","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.YpZnc0IWBVWtIoJO"},"scene-packer":{"sourceId":"Macro.YpZnc0IWBVWtIoJO"}},"ownership":{"default":0,"MRUsyvFBYyl6GZtk":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.2.2","coreVersion":"10.303","createdTime":null,"modifiedTime":1691045149885,"lastModifiedBy":"GmyVtlP5mAtoWJMx"}}
{"_id":"OUvevMPeaRKzQBVu","name":"Bulk Lock/Unlock compendiums","type":"script","author":"MRUsyvFBYyl6GZtk","img":"icons/svg/door-locked-outline.svg","scope":"global","command":"// This macro allows you to bulk lock or unlock compendiums\n// as a way to save time.\n(() => {\n  if (!game.user.isGM) {\n    return;\n  }\n\n  let activeModules = (foundry.utils.isNewerVersion(game.version ?? game.data.version, '10') ? game.modules : game.data.modules)\n    .filter((m) => m.active && ((m.packs ?? m.data?.packs)?.size || m.packs?.length))\n    .map((m) => m.id);\n  if (!activeModules.length) {\n    ui.notifications.info(\n      \"There are no modules with compendium packs in this world.\"\n    );\n    return;\n  }\n  let hasWorldCompendiums = !!game.packs.find(p => (p.metadata.packageName || p.metadata.package) === 'world')\n  let content = `<p>Bulk Lock/Unlock compendium packs that belong to the following module:</p>`;\n  content += '<select id=\"module-name\">';\n  activeModules.forEach((m) => {\n    content += `<option value=\"${m}\">${m}</option>`;\n  });\n  if (hasWorldCompendiums) {\n    content += `<option value=\"world\">world</option>`;\n  }\n  content += \"</select>\";\n  content += \"<p><hr></p>\";\n  let d = new Dialog({\n    title: \"Bulk Lock/Unlock compendium packs\",\n    content: content,\n    buttons: {\n      lock: {\n        icon: '<i class=\"fas fa-lock\"></i>',\n        label: game.i18n.localize(\"Lock\"),\n        callback: async (html) => {\n          await setState(true, html.find(\"#module-name\")[0].value);\n        },\n      },\n      unlock: {\n        icon: '<i class=\"fas fa-unlock\"></i>',\n        label: game.i18n.localize(\"Unlock\"),\n        callback: async (html) => {\n          await setState(false, html.find(\"#module-name\")[0].value);\n        },\n      },\n      cancel: {\n        icon: '<i class=\"fas fa-times\"></i>',\n        label: game.i18n.localize(\"Cancel\"),\n      },\n    },\n  });\n  d.render(true);\n\n  async function setState(locked, moduleName) {\n    locked = !!locked;\n    const compendiums = game.packs.filter(\n      (p) => (p.metadata.packageName || p.metadata.package) === moduleName\n    );\n    const settings = {};\n    compendiums.forEach((p) => {\n      settings[`${(p.metadata.packageName || p.metadata.package)}.${p.metadata.name}`] = { locked };\n    });\n    if (Object.keys(settings).length) {\n      let key = locked\n                ? \"Locking compendiums: {list}\"\n                : \"Unlocking compendiums: {list}\";\n      ui.notifications.info(\n        game.i18n.format(key, {\n          list: Object.keys(settings).join(\", \"),\n          locked: locked,\n        })\n      );\n      let configSetting =\n        Compendium?.CONFIG_SETTING ||\n        CompendiumCollection?.CONFIG_SETTING ||\n        \"compendiumConfiguration\";\n      await game.settings.set(\"core\", configSetting, settings);\n    }\n  }\n})();","folder":null,"sort":0,"permission":{"default":0},"flags":{"scene-packer":{"hash":"9526d9354098381b75dbb4a47300957cbdf45c4b","sourceId":"Macro.cEYqc2chS1vOpIPo"},"core":{"sourceId":"Macro.cEYqc2chS1vOpIPo"}}}
{"name":"Show Scenes Worth Packing","type":"script","author":"XKmxUPdkcWMuKdfu","img":"icons/svg/eye.svg","scope":"global","command":"// This macro will check your world Scenes to see whether\n// there is any data that would benefit from being Packed.\n\nScenePacker.ShowScenesWorthPacking();","folder":null,"sort":0,"permission":{"default":0,"XKmxUPdkcWMuKdfu":3},"flags":{"core":{"sourceId":"Macro.zo5hb9xMPyiUEqrE"}},"_id":"P3UkVzGKhtwAcpRT"}
{"_id":"YsJZfzxws5UeXYJC","name":"Bulk pack scenes","type":"script","author":"FYKEkGQDWeedzh8v","img":"icons/svg/barrel.svg","scope":"global","command":"// This macro will pack/repack all scenes in the requested scene folder.\nDialog.prompt({\n  title: 'Pack all Scenes in a folder',\n  content: '<label>Enter folder name (case sensitive): <input type=\"text\" name=\"folder\"><hr></label>',\n  label: 'Pack scenes',\n  callback: async (html) => {\n    let folderName = html.find('input').val();\n    if (!folderName) {\n      return;\n    }\n    let process = async (f, instance) => {\n      const contents = f.entries ?? f.documents ?? f.contents ?? f.content ?? [];\n      for (const scene of (contents || [])) {\n        await instance.ClearPackedData(scene);\n        await instance.PackScene(scene);\n      }\n      for (const child of (f.children || [])) {\n        await process(child, instance);\n      }\n    }\n    const folder = game.folders.find(f => f.name === folderName && f.type === 'Scene');\n    if (folder) {\n      ScenePacker.PromptForInstance().then(async instance => {\n        await process(folder, instance);\n        ui.notifications.warn('Make sure that you export your packed scenes to your compendium. You should also run \"Relink compendium entries\" one more time after exporting your scenes.', { permanent: true });\n      })\n    } else {\n      console.error(`Could not find a Scene folder with the name \"${folderName}\"`);\n    }\n  },\n});","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.oYPUPtEzf8Dp90ZS"},"scene-packer":{"sourceId":"Macro.oYPUPtEzf8Dp90ZS"}},"ownership":{"default":0,"FYKEkGQDWeedzh8v":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.2.2","coreVersion":"10.303","createdTime":null,"modifiedTime":1691036534605,"lastModifiedBy":"GmyVtlP5mAtoWJMx"}}
{"_id":"Zj82Pme9Bzjd6MPK","name":"Clean up #[CF_tempEntity] entries","type":"script","author":"XbLObTJNibRaLZgl","img":"icons/svg/acid.svg","scope":"global","command":"// Prompt to remove \"#[CF_tempEntity]\" items from the world,\n// in the case where they have been imported when the\n// Compendium Folders module is not active.\n(async () => {\n  if (!game.user.isGM) {\n    return;\n  }\n\n  let cfTempEntity = '#[CF_tempEntity]';\n  let activeModules = (foundry.utils.isNewerVersion(game.version ?? game.data.version, '10') ? game.modules : game.data.modules)\n    .filter((m) => m.active && ((m.packs ?? m.data?.packs)?.size || m.packs?.length))\n    .map((m) => m.id);\n  let content = `<p>Remove #[CF_tempEntity] from the following location:</p>`;\n  content += '<select id=\"module-name\">';\n  content += `<option value=\"world\" selected>world</option>`;\n  activeModules.forEach((m) => {\n    content += `<option value=\"${m}\">${m}</option>`;\n  });\n  content += '</select>';\n  content += '<p><hr></p>';\n  new Dialog({\n    title: 'Remove #[CF_tempEntity]',\n    content: content,\n    buttons: {\n      process: {\n        icon: '<i class=\"fas fa-check\"></i>',\n        label: game.i18n.localize('Process'),\n        callback: async (html) => {\n          await process(html.find('#module-name')[0].value);\n        },\n      },\n      cancel: {\n        icon: '<i class=\"fas fa-times\"></i>',\n        label: game.i18n.localize('Cancel'),\n      },\n    },\n  }).render(true);\n\n  async function process(moduleName) {\n    let collections = [\n      {name: 'scenes', collection: game.scenes},\n      {name: 'actors', collection: game.actors},\n      {name: 'items', collection: game.items},\n      {name: 'journals', collection: game.journal},\n      {name: 'macros', collection: game.macros},\n      {name: 'playlists', collection: game.playlists},\n      {name: 'roll tables', collection: game.tables},\n    ];\n    let packs = game.packs.filter(p => (p.metadata.packageName || p.metadata.package) === moduleName)\n    for (const entity of collections) {\n      const found = [];\n      if (moduleName === 'world') {\n        found.push(...entity.collection.filter(e => e.name === cfTempEntity));\n      }\n      for (const pack of packs) {\n        const packType = pack.documentName || pack.entity;\n        if (packType !== entity.collection.documentName) {\n          continue;\n        }\n        pack.configure({locked: false});\n        const docs = await pack.getDocuments();\n        found.push(...docs.filter(e => e.name === cfTempEntity));\n      }\n      if (found.length) {\n        for (const foundElement of found) {\n          console.log(`Deleting ${foundElement.id} from ${entity.name}`);\n          await foundElement.delete();\n        }\n      } else {\n        console.log(`No ${entity.name} with the name \"${cfTempEntity}\" found.`);\n      }\n    }\n  }\n})();","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.Vseli28UPwE5waan"},"scene-packer":{"sourceId":"Macro.mYvPx3E5TVzLprkb","hash":"dd9e25978da2a949c96605a1789e171694d2442f"}},"ownership":{"default":0,"XbLObTJNibRaLZgl":3},"_stats":{"systemId":"dnd5e","systemVersion":"1.6.1","coreVersion":"10.263","createdTime":null,"modifiedTime":1653106037269,"lastModifiedBy":"sH4kZUxETJcGJ0Zj"}}
{"name":"Show asset report","type":"script","author":"ajyvWN29UcpExc6d","img":"icons/svg/hazard.svg","scope":"global","command":"// This macro shows an asset report that details the assets\n// in this world and whether they have \"external\" dependencies.\n//\n// Use it to help determine whether your packaged module will\n// result in broken assets (images, sounds etc.).\n\nnew ScenePacker.AssetReport();","folder":null,"sort":0,"permission":{"default":0,"ajyvWN29UcpExc6d":3},"flags":{"core":{"sourceId":"Macro.CIKJ4iAmyMvhtptI"}},"_id":"mkNKay70v790Fu4h"}
{"_id":"qIQ9ehnHD47ekm1l","name":"Reset world Scene Packer prompts","type":"script","author":"Rt2brfVFebtCuVnc","img":"icons/svg/hanging-sign.svg","scope":"global","command":"// This macro will prompt you to select your module and then\n// will reset the settings values to allow retriggering of\n// the prompts when you first install a world.\n//\n// These prompts are the \"Yes import all\", \"Let me choose\" etc.\n\nlet content = '';\nlet instances = Object.keys(ScenePacker.instances);\nif (instances.length) {\n  content = '<p>Select the module that you want to reset prompting for:</p>';\n  content += '<select id=\"module-name\">';\n  instances.forEach(m => {\n    content += `<option value=\"${m}\">${m}</option>`;\n  });\n  content += '</select>';\n  content += '<p>Refresh the world after resetting.</p>'\n} else {\n  content = '<p>There are no modules registered with Scene Packer currently available.</p>';\n}\nlet d = new Dialog({\n  title: 'Select instance',\n  content: content,\n  buttons: {\n    relink: {\n      icon: '<i class=\"fas fa-check\"></i>',\n      label: 'Reset',\n      callback: (html) => {\n        let moduleName = html.find('#module-name')[0].value;\n        if (!moduleName) {\n          return;\n        }\n        game.settings.set(moduleName, 'imported', '0.0.0');\n        game.settings.set(moduleName, 'prompted', '0.0.0');\n        game.settings.set(moduleName, 'showWelcomePrompts', true);\n        game.settings.set(moduleName, 'migrationSystemVersion', '0.0.0')\n        if (canvas?.scene?.getFlag(moduleName, 'imported')) {\n          canvas.scene.setFlag(moduleName, 'imported', '0.0.0')\n        }\n      },\n    },\n    cancel: {\n      icon: '<i class=\"fas fa-times\"></i>',\n      label: 'Cancel',\n    },\n  },\n});\nd.render(true);","folder":null,"sort":0,"flags":{"core":{"sourceId":"Macro.8Ed2EtweXHL8UrPq"},"scene-packer":{"sourceId":"Macro.8Ed2EtweXHL8UrPq"}},"ownership":{"default":0,"Rt2brfVFebtCuVnc":3},"_stats":{"systemId":"dnd5e","systemVersion":"2.0.2","coreVersion":"10.285","createdTime":null,"modifiedTime":1663143118043,"lastModifiedBy":"rfTqhTOpsOhdbu6t"}}
{"name":"Bulk Unpack Scenes","type":"script","author":"xSwMQP4A8LjT1qsd","img":"icons/svg/up.svg","scope":"global","command":"// This macro will unpack any scenes in your world that have\n// packed data. It can be used to prevent you needing to go\n// through activating each scene one by one.\n(async () => {\n  ScenePacker.PromptForInstance().then(async instance => {\n    if (!instance?.moduleName) {\n      return;\n    }\n\n    const scenes = [];\n    for (const scene of game.scenes) {\n      if (ScenePacker.HasPackedData(scene, instance.moduleName)) {\n        scenes.push(scene);\n      }\n    }\n    if (!scenes.length) {\n      return ui.notifications.info(`There are no scenes that need unpacking in \"${instance.moduleName}\".`);\n    }\n\n    ui.notifications.info(`Unpacking ${scenes.length} scenes in \"${instance.moduleName}\".`);\n    console.log(`Unpacking ${scenes.length} scenes in \"${instance.moduleName}\".`);\n    for (const scene of scenes) {\n      await instance.ProcessScene(scene, {showLinkedJournal: false, showUI: false});\n    }\n    ui.notifications.info(`Done. Unpacked ${scenes.length} scenes in \"${instance.moduleName}\".`);\n    console.log(`Done. Unpacked ${scenes.length} scenes in \"${instance.moduleName}\".`);\n  })\n})();","folder":null,"sort":0,"permission":{"default":0,"xSwMQP4A8LjT1qsd":3},"flags":{"scene-packer":{"hash":"013cdcf87f0a7771fdc86d1201d06f4d48facd43","sourceId":"Macro.EKufWVdIaK6oCGSA"},"core":{"sourceId":"Macro.F7vTa4gVwxXpXzUM"}},"_id":"yHihw7i94MaqYKQj"}
