Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions dnd5e.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ Hooks.once("init", function() {
label: "DND5E.SheetClass.Encounter"
});

DocumentSheetConfig.registerSheet(Adventure, "dnd5e", applications.adventure.AdventureImporter5e, {
canBeDefault: false,
label: "DND5E.SheetClass.AdventureImporter"
});

DocumentSheetConfig.unregisterSheet(Item, "core", foundry.appv1.sheets.ItemSheet);
DocumentSheetConfig.registerSheet(Item, "dnd5e", applications.item.ItemSheet5e, {
makeDefault: true,
Expand Down Expand Up @@ -566,8 +571,19 @@ Hooks.once("ready", function() {
dnd5e.ui.calendar.render({ force: true });
}

// Determine whether a system migration is required and feasible
// Run migrations & post-import actions for quickstarted adventures
_handleMigration()
.then(() => applications.adventure.AdventureQuickstartDialog.handleQuickstart());
});

/* -------------------------------------------- */

/**
* Determine whether a system migration is required and feasible and run it.
*/
async function _handleMigration() {
if ( !game.user.isGM ) return;

const cv = game.settings.get("dnd5e", "systemMigrationVersion") || game.world.flags.dnd5e?.version;
const totalDocuments = game.actors.size + game.scenes.size + game.items.size;
if ( !cv && totalDocuments === 0 ) return game.settings.set("dnd5e", "systemMigrationVersion", game.system.version);
Expand All @@ -582,8 +598,9 @@ Hooks.once("ready", function() {
if ( cv && foundry.utils.isNewerVersion(game.system.flags.compatibleMigrationVersion, cv) ) {
ui.notifications.error("MIGRATION.DND5E.Warning.VersionTooOld", { permanent: true });
}
migrations.migrateWorld();
});

await migrations.migrateWorld();
}

/* -------------------------------------------- */
/* System Styling */
Expand Down
22 changes: 22 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,27 @@
"DND5E.AdvancementTitle": "Advancement",
"DND5E.Advantage": "Advantage",
"DND5E.AdvantageMode": "Advantage Mode",

"DND5E.ADVENTURE": {
"Action": {
"Configure": "Configure Adventure",
"Launch": "Launch Adventure"
},
"Finished": {
"one": "Finished preparing {adventure}.",
"other": "Finished preparing {number} adventures."
},
"ImportAction": {
"ActivateScene": "Activate Initial Scene",
"CustomizeWorld": "Customize World Details",
"DisplayJournal": "Display Getting Started Journal"
},
"Warning": {
"JournalMissing": "Journal entry `{id}` could not be found to display for {adventure}.",
"SceneMissing": "Scene `{id}` could not be found to activate for {adventure}."
}
},

"DND5E.Age": "Age",
"DND5E.Alignment": "Alignment",
"DND5E.AlignmentCE": "Chaotic Evil",
Expand Down Expand Up @@ -4346,6 +4367,7 @@
"DND5E.Shape": "Shape",

"DND5E.SheetClass": {
"AdventureImporter": "D&D 5e Adventure Importer",
"Character": "D&D 5e Character Sheet",
"ClassSummary": "D&D 5e Class Summary Sheet",
"Container": "D&D 5e Container Sheet",
Expand Down
26 changes: 26 additions & 0 deletions less/v2/sheets.less
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,32 @@ body.detached .dnd5e2.sheet .tab { max-height: unset; }
&:not(.locked) .rollable.draggable { cursor: var(--cursor-pointer); }
}

/* ---------------------------------- */
/* Adventure Importer */
/* ---------------------------------- */

.dnd5e2.adventure-importer {
[data-application-part="body"] { gap: 10px; }
.adventure-contents {
display: grid;
grid-template-areas:
"overview controls"
"overview options";
gap: 1rem 2rem;

.adventure-overview { grid-area: overview; }
.import-controls { grid-area: controls; }
.import-options { grid-area: options; }

h2 { font-size: var(--font-h2-size); }
}
.import-controls .form-group { margin-block-end: 4px; }
}

.dnd5e2.adventure-quickstart {
.standard-form .form-group label { flex: 6; }
}

/* ---------------------------------- */
/* Roll Table Sheets */
/* ---------------------------------- */
Expand Down
50 changes: 50 additions & 0 deletions module/_types.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,56 @@

/* -------------------------------------------- */

/**
* @typedef AdventureConfiguration
* @property {AdventureImportAction[]} importActions Actions performed when the adventure is imported.
*/

/* -------------------------------------------- */

/**
* @typedef AdventureImportAction
* @property {string} id Unique ID for this import action.
* @property {string} label Localized label for the option.
* @property {AdventureImportPreHandler|AdventureImportPostHandler} handler Handler function to call.
* @property {"pre"|"post"} lifecycle Should this handler be called before or after importing.
* @property {boolean} [default=false] Should this option be checked by default on the adventure importer?
* @property {AdventureImportQuickstartHandler} [quickstartHandler] Handler called when after quickstarting a module.
* Must be set to use an action during quickstart.
*/

/* -------------------------------------------- */

/**
* @callback AdventureImportPostHandler
* @this {Adventure}
* @param {AdventureImportAction} config
* @param {AdventureImportResult} importResult
* @param {AdventureImportOptions} importOptions
* @returns {Promise<*>}
*/

/* -------------------------------------------- */

/**
* @callback AdventureImportPreHandler
* @this {Adventure}
* @param {AdventureImportAction} config
* @param {AdventureImportData} importData
* @param {AdventureImportOptions} importOptions
* @returns {Promise<*>}
*/

/* -------------------------------------------- */

/**
* @callback AdventureImportQuickstartHandler
* @param {{ adventure: Adventure, config: AdventureImportAction }[]} adventures
* @returns {Promise<*>}
*/

/* -------------------------------------------- */

/**
* Information needed to represent different area of effect target types.
*
Expand Down
1 change: 1 addition & 0 deletions module/applications/_module.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * as activity from "./activity/_module.mjs";
export * as actor from "./actor/_module.mjs";
export * as advancement from "./advancement/_module.mjs";
export * as adventure from "./adventure/_module.mjs";
export * as api from "./api/_module.mjs";
export * as calendar from "./calendar/_module.mjs";
export * as combat from "./combat/_module.mjs";
Expand Down
2 changes: 2 additions & 0 deletions module/applications/adventure/_module.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {default as AdventureImporter5e} from "./importer.mjs";
export {default as AdventureQuickstartDialog} from "./quickstart-dialog.mjs";
96 changes: 96 additions & 0 deletions module/applications/adventure/importer.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { filteredKeys } from "../../utils.mjs";
import ApplicationV2Mixin from "../api/application-v2-mixin.mjs";

const { AdventureImporterV2 } = foundry.applications.sheets;
const { BooleanField, SchemaField } = foundry.data.fields;

/**
* Extension of the default adventure importer with system-specific design and pre/post action support.
*/
export default class AdventureImporter5e extends ApplicationV2Mixin(AdventureImporterV2, { handlebars: false }) {

/** @inheritDoc */
static PARTS = {
...super.PARTS,
body: {
template: "systems/dnd5e/templates/adventure/importer.hbs"
}
};

/* -------------------------------------------- */
/* Properties */
/* -------------------------------------------- */

/** @override */
get title() {
return this.document.name;
}

/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */

/** @override */
_prepareImportOptionsSchema(options) {
const importActions = this.adventure.importActions;
if ( !importActions.length ) return;
const selected = game.settings.get("core", "adventureImports")[this.adventure.uuid]?.options?.actions ?? {};
const fields = {};
for ( const action of importActions ) {
fields[`actions.${action.id}`] = new BooleanField({
initial: selected[action.id] ?? action.default, label: action.label
});
}
return new SchemaField(fields);
}

/* -------------------------------------------- */
/* Import Workflows */
/* -------------------------------------------- */

/** @inheritDoc */
async _configureImport(importOptions) {
await super._configureImport(importOptions);
importOptions.actions = filteredKeys(importOptions.actions);
}

/* -------------------------------------------- */

/** @inheritDoc */
async _preImport(importData, importOptions) {
await super._preImport(importData, importOptions);
const actions = this.adventure.importActions
.filter(o => o.lifecycle === "pre" && importOptions.actions?.includes(o.id));
for ( const action of actions ) {
await action.handler.call(this.adventure, action, importData, importOptions);
}
}

/* -------------------------------------------- */

/** @inheritDoc */
async _onImport(importResult, importOptions) {
await super._onImport(importResult, importOptions);
const actions = this.adventure.importActions
.filter(o => o.lifecycle === "post" && importOptions.actions?.includes(o.id));
for ( const action of actions ) {
await action.handler.call(this.adventure, action, importResult, importOptions);
}
}

/* -------------------------------------------- */
/* Event Listeners and Handlers */
/* -------------------------------------------- */

/** @override */
_onToggleImportAll(event) {
const target = event.target;
const section = target.closest(".import-controls");
const checked = target.checked;
section.querySelectorAll("dnd5e-checkbox").forEach(input => {
if ( input === target ) return;
if ( input.value !== "folders" ) input.disabled = checked;
if ( checked ) input.checked = true;
});
}
}
Loading