diff --git a/daggerheart.mjs b/daggerheart.mjs
index dbe5aa21..071a8bce 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -79,6 +79,7 @@ Hooks.once('init', () => {
Items.registerSheet(SYSTEM.id, applications.DhpConsumable, { types: ['consumable'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.DhpWeapon, { types: ['weapon'], makeDefault: true });
Items.registerSheet(SYSTEM.id, applications.DhpArmor, { types: ['armor'], makeDefault: true });
+ Items.registerSheet(SYSTEM.id, applications.DhBeastform, { types: ['beastform'], makeDefault: true });
CONFIG.Actor.documentClass = documents.DhpActor;
CONFIG.Actor.dataModels = models.actors.config;
diff --git a/lang/en.json b/lang/en.json
index 2277f6ab..30673555 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -10,7 +10,8 @@
"consumable": "Consumable",
"miscellaneous": "Miscellaneous",
"weapon": "Weapon",
- "armor": "Armor"
+ "armor": "Armor",
+ "beastform": "Beastform"
},
"Actor": {
"character": "Character",
@@ -1461,8 +1462,18 @@
}
}
},
+ "Beastform": {
+ "DialogTitle": "Beastform Selection",
+ "FIELDS": {
+ "tier": { "label": "Tier" },
+ "examples": { "label": "Examples" },
+ "advantageOn": { "label": "Gain Advantage On" }
+ },
+ "Transform": "Transform"
+ },
"Global": {
"Actions": "Actions",
+ "Features": "Features",
"Effects": "Effects",
"activeEffects": "Active Effects",
"inativeEffects": "Inative Effects"
diff --git a/module/applications/_module.mjs b/module/applications/_module.mjs
index a74cb8cf..a2b82a75 100644
--- a/module/applications/_module.mjs
+++ b/module/applications/_module.mjs
@@ -15,5 +15,6 @@ export { default as DhpChatMessage } from './chatMessage.mjs';
export { default as DhpEnvironment } from './sheets/actors/environment.mjs';
export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs';
export { default as DhContextMenu } from './contextMenu.mjs';
+export { default as DhBeastform } from './sheets/items/beastform.mjs';
export * as api from './sheets/api/_modules.mjs';
diff --git a/module/applications/sheets/items/beastform.mjs b/module/applications/sheets/items/beastform.mjs
new file mode 100644
index 00000000..12134558
--- /dev/null
+++ b/module/applications/sheets/items/beastform.mjs
@@ -0,0 +1,62 @@
+import DHBaseItemSheet from '../api/base-item.mjs';
+
+export default class BeastformSheet extends DHBaseItemSheet {
+ /**@inheritdoc */
+ static DEFAULT_OPTIONS = {
+ classes: ['beastform'],
+ dragDrop: [{ dragSelector: null, dropSelector: '.drop-section' }],
+ actions: {
+ editFeature: this.editFeature,
+ removeFeature: this.removeFeature
+ }
+ };
+
+ /**@override */
+ static PARTS = {
+ header: { template: 'systems/daggerheart/templates/sheets/items/beastform/header.hbs' },
+ tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
+ settings: { template: 'systems/daggerheart/templates/sheets/items/beastform/settings.hbs' },
+ features: {
+ template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
+ scrollable: ['.features']
+ }
+ };
+
+ static TABS = {
+ primary: {
+ tabs: [{ id: 'settings' }, { id: 'features' }],
+ initial: 'settings',
+ labelPrefix: 'DAGGERHEART.Sheets.TABS'
+ }
+ };
+
+ /**@inheritdoc */
+ async _preparePartContext(partId, context) {
+ await super._preparePartContext(partId, context);
+
+ return context;
+ }
+
+ static editFeature(event) {
+ const target = event.target.closest('[data-action="editFeature"]');
+ const feature = this.document.system.features[target.dataset.index];
+ feature.sheet.render(true);
+ }
+
+ static async removeFeature(_, target) {
+ const current = this.document.system.features.map(x => x.uuid);
+ await this.document.update({
+ 'system.features': current.filter((_, index) => index !== Number(target.dataset.index))
+ });
+ this.render();
+ }
+
+ async _onDrop(event) {
+ const data = TextEditor.getDragEventData(event);
+ const item = await fromUuid(data.uuid);
+ if (item.type === 'feature') {
+ const current = this.document.system.features.map(x => x.uuid);
+ await this.document.update({ 'system.features': [...current, item.uuid] });
+ }
+ }
+}
diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs
index 6710d3ed..06c75fce 100644
--- a/module/config/generalConfig.mjs
+++ b/module/config/generalConfig.mjs
@@ -245,19 +245,23 @@ export const deathMoves = {
export const tiers = {
tier1: {
id: 'tier1',
- label: 'DAGGERHEART.Tiers.tier1'
+ label: 'DAGGERHEART.Tiers.tier1',
+ value: 1
},
tier2: {
id: 'tier2',
- label: 'DAGGERHEART.Tiers.tier2'
+ label: 'DAGGERHEART.Tiers.tier2',
+ value: 2
},
tier3: {
id: 'tier3',
- label: 'DAGGERHEART.Tiers.tier3'
+ label: 'DAGGERHEART.Tiers.tier3',
+ value: 3
},
tier4: {
id: 'tier4',
- label: 'DAGGERHEART.Tiers.tier4'
+ label: 'DAGGERHEART.Tiers.tier4',
+ value: 4
}
};
diff --git a/module/data/action/action.mjs b/module/data/action/action.mjs
index 00f3ef2c..eac05a7f 100644
--- a/module/data/action/action.mjs
+++ b/module/data/action/action.mjs
@@ -2,6 +2,7 @@ import CostSelectionDialog from '../../applications/costSelectionDialog.mjs';
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField } from './actionDice.mjs';
import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../dialogs/d20RollDialog.mjs';
+import BeastformDialog from '../../dialogs/beastformDialog.mjs';
const fields = foundry.data.fields;
@@ -271,7 +272,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
}
if (this instanceof DhBeastformAction) {
- console.log('Test');
+ config = await BeastformDialog.configure(config);
+ if (!config) return;
}
if (this.doFollowUp()) {
diff --git a/module/data/item/_module.mjs b/module/data/item/_module.mjs
index da3bf2d4..a29d1595 100644
--- a/module/data/item/_module.mjs
+++ b/module/data/item/_module.mjs
@@ -8,6 +8,7 @@ import DHFeature from './feature.mjs';
import DHMiscellaneous from './miscellaneous.mjs';
import DHSubclass from './subclass.mjs';
import DHWeapon from './weapon.mjs';
+import DHBeastform from './beastform.mjs';
export {
DHAncestry,
@@ -19,7 +20,8 @@ export {
DHFeature,
DHMiscellaneous,
DHSubclass,
- DHWeapon
+ DHWeapon,
+ DHBeastform
};
export const config = {
@@ -32,5 +34,6 @@ export const config = {
feature: DHFeature,
miscellaneous: DHMiscellaneous,
subclass: DHSubclass,
- weapon: DHWeapon
+ weapon: DHWeapon,
+ beastform: DHBeastform
};
diff --git a/module/data/item/beastform.mjs b/module/data/item/beastform.mjs
new file mode 100644
index 00000000..8df5dd0e
--- /dev/null
+++ b/module/data/item/beastform.mjs
@@ -0,0 +1,32 @@
+import ActionField from '../fields/actionField.mjs';
+import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
+import BaseDataItem from './base.mjs';
+
+export default class DHBeastform extends BaseDataItem {
+ static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Beastform'];
+
+ /** @inheritDoc */
+ static get metadata() {
+ return foundry.utils.mergeObject(super.metadata, {
+ label: 'TYPES.Item.beastform',
+ type: 'beastform',
+ hasDescription: false
+ });
+ }
+
+ /** @inheritDoc */
+ static defineSchema() {
+ const fields = foundry.data.fields;
+ return {
+ ...super.defineSchema(),
+ tier: new fields.StringField({
+ required: true,
+ choices: SYSTEM.GENERAL.tiers,
+ initial: SYSTEM.GENERAL.tiers.tier1.id
+ }),
+ examples: new fields.StringField(),
+ advantageOn: new fields.ArrayField(new fields.StringField()),
+ features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
+ };
+ }
+}
diff --git a/module/dialogs/beastformDialog.mjs b/module/dialogs/beastformDialog.mjs
new file mode 100644
index 00000000..d9699b59
--- /dev/null
+++ b/module/dialogs/beastformDialog.mjs
@@ -0,0 +1,83 @@
+import { tiers } from '../config/generalConfig.mjs';
+
+const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
+
+export default class BeastformDialog extends HandlebarsApplicationMixin(ApplicationV2) {
+ constructor(configData) {
+ super();
+
+ this.configData = configData;
+ this.selected = null;
+ }
+
+ static DEFAULT_OPTIONS = {
+ tag: 'form',
+ id: 'beastform-selection',
+ classes: ['daggerheart', 'views', 'dh-style', 'beastform-selection'],
+ position: {
+ width: 600,
+ height: 'auto'
+ },
+ actions: {
+ submitBeastform: this.submitBeastform
+ },
+ form: {
+ handler: this.updateBeastform,
+ submitOnChange: true,
+ submitOnClose: false
+ }
+ };
+
+ get title() {
+ return game.i18n.localize('DAGGERHEART.Sheets.Beastform.DialogTitle');
+ }
+
+ /** @override */
+ static PARTS = {
+ beastform: {
+ template: 'systems/daggerheart/templates/views/beastformDialog.hbs'
+ }
+ };
+
+ async _prepareContext(_options) {
+ const context = await super._prepareContext(_options);
+ context.configData = this.configData;
+
+ const tierLimit = 2;
+ context.beastformTiers = game.items.reduce((acc, x) => {
+ const tier = tiers[x.system.tier];
+ if (x.type !== 'beastform' || tier.value > tierLimit) return acc;
+
+ if (!acc[tier.value]) acc[tier.value] = { label: game.i18n.localize(tier.label), values: {} };
+ acc[tier.value].values[x.uuid] = { selected: this.selected == x.uuid, value: x };
+
+ return acc;
+ }, {}); // Also get from compendium when added
+ context.canSubmit = this.selected;
+
+ return context;
+ }
+
+ static updateBeastform(event, _, formData) {
+ this.selected = foundry.utils.mergeObject(this.selected, formData.object);
+
+ this.render();
+ }
+
+ static async submitBeastform() {
+ await this.close({ submitted: true });
+ }
+
+ /** @override */
+ _onClose(options = {}) {
+ if (!options.submitted) this.config = false;
+ }
+
+ static async configure(configData) {
+ return new Promise(resolve => {
+ const app = new this(configData);
+ app.addEventListener('close', () => resolve(app.config), { once: true });
+ app.render({ force: true });
+ });
+ }
+}
diff --git a/styles/daggerheart.css b/styles/daggerheart.css
index 062dd5a3..73b4750c 100755
--- a/styles/daggerheart.css
+++ b/styles/daggerheart.css
@@ -4996,6 +4996,53 @@ div.daggerheart.views.multiclass {
color: light-dark(#18162e50, #efe6d850);
font-family: 'Montserrat', sans-serif;
}
+.theme-light .application.daggerheart.dh-style.views.beastform-selection .beastforms-container .beastforms-tier .beastform-container .beastform-title {
+ background-image: url('../assets/parchments/dh-parchment-dark.png');
+}
+.application.daggerheart.dh-style.views.beastform-selection .beastforms-container {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+.application.daggerheart.dh-style.views.beastform-selection .beastforms-container .beastforms-tier {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 4px;
+}
+.application.daggerheart.dh-style.views.beastform-selection .beastforms-container .beastforms-tier .beastform-container {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ border: 1px solid light-dark(#18162e, #f3c267);
+ border-radius: 6px;
+ cursor: pointer;
+}
+.application.daggerheart.dh-style.views.beastform-selection .beastforms-container .beastforms-tier .beastform-container.disabled {
+ opacity: 0.4;
+}
+.application.daggerheart.dh-style.views.beastform-selection .beastforms-container .beastforms-tier .beastform-container img {
+ width: 100%;
+ border-radius: 6px;
+}
+.application.daggerheart.dh-style.views.beastform-selection .beastforms-container .beastforms-tier .beastform-container .beastform-title {
+ position: absolute;
+ top: 4px;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 16px;
+ margin: 0 4px;
+ border: 1px solid light-dark(#18162e, #f3c267);
+ border-radius: 6px;
+ color: light-dark(#efe6d8, #222);
+ background-image: url('../assets/parchments/dh-parchment-light.png');
+}
+.application.daggerheart.dh-style.views.beastform-selection footer {
+ margin-top: 8px;
+ display: flex;
+}
+.application.daggerheart.dh-style.views.beastform-selection footer button {
+ flex: 1;
+}
.application.sheet.daggerheart.actor.dh-style.companion .profile {
height: 80px;
width: 80px;
@@ -5703,6 +5750,44 @@ div.daggerheart.views.multiclass {
justify-content: center;
gap: 10px;
}
+.sheet.daggerheart.dh-style .tab.features .features-list {
+ display: flex;
+ flex-direction: column;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ gap: 5px;
+}
+.sheet.daggerheart.dh-style .tab.features .features-list .feature-item {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 1fr 4fr 1fr;
+ cursor: pointer;
+}
+.sheet.daggerheart.dh-style .tab.features .features-list .feature-item img {
+ height: 40px;
+ width: 40px;
+ object-fit: cover;
+ border-radius: 3px;
+}
+.sheet.daggerheart.dh-style .tab.features .features-list .feature-item h4 {
+ font-family: 'Montserrat', sans-serif;
+ font-weight: lighter;
+ color: #efe6d8;
+}
+.sheet.daggerheart.dh-style .tab.features .features-list .feature-item .image {
+ height: 40px;
+ width: 40px;
+ object-fit: cover;
+ border-radius: 6px;
+ border: none;
+}
+.sheet.daggerheart.dh-style .tab.features .features-list .feature-item .controls {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
.sheet.daggerheart.dh-style .tab.effects .effects-list {
display: flex;
flex-direction: column;
diff --git a/styles/daggerheart.less b/styles/daggerheart.less
index 362856ce..b915d44a 100755
--- a/styles/daggerheart.less
+++ b/styles/daggerheart.less
@@ -40,6 +40,8 @@
@import './less/applications/environment-settings/actions.less';
@import './less/applications/environment-settings/adversaries.less';
+@import './less/applications//beastform.less';
+
@import './less/actors/companion/sheet.less';
@import './less/actors/adversary.less';
@@ -58,6 +60,7 @@
@import './less/global/tab-navigation.less';
@import './less/global/tab-form-footer.less';
@import './less/global/tab-actions.less';
+@import './less/global/tab-features.less';
@import './less/global/tab-effects.less';
@import './less/global/item-header.less';
@import './less/global/feature-section.less';
diff --git a/styles/less/applications/beastform.less b/styles/less/applications/beastform.less
new file mode 100644
index 00000000..e809bedd
--- /dev/null
+++ b/styles/less/applications/beastform.less
@@ -0,0 +1,59 @@
+.theme-light .application.daggerheart.dh-style.views.beastform-selection {
+ .beastforms-container .beastforms-tier .beastform-container .beastform-title {
+ background-image: url('../assets/parchments/dh-parchment-dark.png');
+ }
+}
+
+.application.daggerheart.dh-style.views.beastform-selection {
+ .beastforms-container {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ .beastforms-tier {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 4px;
+
+ .beastform-container {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ border-radius: 6px;
+ cursor: pointer;
+
+ &.disabled {
+ opacity: 0.4;
+ }
+
+ img {
+ width: 100%;
+ border-radius: 6px;
+ }
+
+ .beastform-title {
+ position: absolute;
+ top: 4px;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 16px;
+ margin: 0 4px;
+ border: 1px solid light-dark(@dark-blue, @golden);
+ border-radius: 6px;
+ color: light-dark(@beige, @dark);
+ background-image: url('../assets/parchments/dh-parchment-light.png');
+ }
+ }
+ }
+ }
+
+ footer {
+ margin-top: 8px;
+ display: flex;
+
+ button {
+ flex: 1;
+ }
+ }
+}
diff --git a/styles/less/global/tab-features.less b/styles/less/global/tab-features.less
new file mode 100644
index 00000000..91335fd5
--- /dev/null
+++ b/styles/less/global/tab-features.less
@@ -0,0 +1,50 @@
+@import '../utils/colors.less';
+@import '../utils/fonts.less';
+
+.sheet.daggerheart.dh-style {
+ .tab.features {
+ .features-list {
+ display: flex;
+ flex-direction: column;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ gap: 5px;
+
+ .feature-item {
+ display: grid;
+ align-items: center;
+ grid-template-columns: 1fr 4fr 1fr;
+ cursor: pointer;
+
+ img {
+ height: 40px;
+ width: 40px;
+ object-fit: cover;
+ border-radius: 3px;
+ }
+
+ h4 {
+ font-family: @font-body;
+ font-weight: lighter;
+ color: @beige;
+ }
+
+ .image {
+ height: 40px;
+ width: 40px;
+ object-fit: cover;
+ border-radius: 6px;
+ border: none;
+ }
+
+ .controls {
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+ }
+ }
+ }
+ }
+}
diff --git a/system.json b/system.json
index 9b22d117..864e540e 100644
--- a/system.json
+++ b/system.json
@@ -247,7 +247,8 @@
},
"armor": {
"htmlFields": ["description"]
- }
+ },
+ "beastform": {}
},
"Combat": {
"combat": {}
diff --git a/templates/sheets/global/partials/inventory-fieldset-items.hbs b/templates/sheets/global/partials/inventory-fieldset-items.hbs
index 0051d6df..cd99fe17 100644
--- a/templates/sheets/global/partials/inventory-fieldset-items.hbs
+++ b/templates/sheets/global/partials/inventory-fieldset-items.hbs
@@ -14,11 +14,6 @@
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=action type=../type}}
{{/if}}
{{/each}}
- {{#each document.system.ancestry.system.actions as |action|}}
- {{#if (or (eq ../type 'ancestry'))}}
- {{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=action type=../type}}
- {{/if}}
- {{/each}}
{{#each document.system.class.value.system.classFeatures as |classFeature|}}
{{#if (or (eq ../type 'class'))}}
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=classFeature type=../type}}
diff --git a/templates/sheets/global/tabs/tab-features.hbs b/templates/sheets/global/tabs/tab-features.hbs
new file mode 100644
index 00000000..59a43655
--- /dev/null
+++ b/templates/sheets/global/tabs/tab-features.hbs
@@ -0,0 +1,23 @@
+
+
{{localize 'TYPES.Item.beastform'}}
+