diff --git a/lang/en.json b/lang/en.json
index 18812e19..5e504bf1 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -106,6 +106,10 @@
"horderHp": "Horde/HP"
},
"Character": {
+ "advantageSources": {
+ "label": "Advantage Sources",
+ "hint": "Add single words or short text as reminders and hints of what a character has advantage on."
+ },
"age": "Age",
"companionFeatures": "Companion Features",
"contextMenu": {
@@ -117,6 +121,10 @@
"unequip": "Unequip",
"useItem": "Use Item"
},
+ "disadvantageSources": {
+ "label": "Disadvantage Sources",
+ "hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
+ },
"faith": "Faith",
"levelUp": "You can level up",
"pronouns": "Pronouns",
@@ -1014,6 +1022,7 @@
},
"Advantage": {
"full": "Advantage",
+ "plural": "Advantages",
"short": "Adv"
},
"Adversary": {
@@ -1169,6 +1178,11 @@
"hint": "The cost in stress you can pay to reduce minor damage to none."
}
}
+ },
+ "attack": {
+ "damage": {
+ "value": { "label": "Base Attack: Damage" }
+ }
}
},
"Tabs": {
@@ -1266,6 +1280,7 @@
"title": "Title",
"true": "True",
"type": "Type",
+ "unarmedStrike": "Unarmed Strike",
"unarmored": "Unarmored",
"use": "Use",
"used": "Used",
@@ -1327,8 +1342,10 @@
"beastformEffect": "Beastform Transformation",
"evolve": "Evolve",
"evolvedFeatureTitle": "Evolved",
+ "evolvedDrag": "Drag a form here to evolve it.",
"hybridize": "Hybridize",
- "hybridFeatureTitle": "Hybrid Features"
+ "hybridizeFeatureTitle": "Hybrid Features",
+ "hybridizeDrag": "Drag a form here to hybridize it."
},
"Class": {
"hopeFeatures": "Hope Features",
@@ -1573,7 +1590,9 @@
"featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.",
"featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.",
"featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here.",
- "beastformMissingEffect": "The Beastform is missing a Beastform Effect. Cannot be used."
+ "beastformMissingEffect": "The Beastform is missing a Beastform Effect. Cannot be used.",
+ "beastformToManyAdvantages": "You cannot select any more advantages.",
+ "beastformToManyFeatures": "You cannot select any more features."
},
"Tooltip": {
"openItemWorld": "Open Item World",
diff --git a/module/applications/dialogs/beastformDialog.mjs b/module/applications/dialogs/beastformDialog.mjs
index db9f9da5..8e0b1f25 100644
--- a/module/applications/dialogs/beastformDialog.mjs
+++ b/module/applications/dialogs/beastformDialog.mjs
@@ -7,7 +7,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
this.configData = configData;
this.selected = null;
this.evolved = { form: null };
- this.hybrid = null;
+ this.hybrid = { forms: {}, advantages: {}, features: {} };
this._dragDrop = this._createDragDropHandlers();
}
@@ -21,6 +21,8 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
},
actions: {
selectBeastform: this.selectBeastform,
+ toggleHybridFeature: this.toggleHybridFeature,
+ toggleHybridAdvantage: this.toggleHybridAdvantage,
submitBeastform: this.submitBeastform
},
form: {
@@ -38,7 +40,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
/** @override */
static PARTS = {
beastform: {
- template: 'systems/daggerheart/templates/dialogs/beastformDialog.hbs'
+ template: 'systems/daggerheart/templates/dialogs/beastform/beastformDialog.hbs'
}
};
@@ -65,7 +67,33 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
context.selectedBeastformEffect = this.selected?.effects?.find?.(x => x.type === 'beastform');
context.evolved = this.evolved;
- context.hybrid = this.hybrid;
+
+ context.hybridForms = Object.keys(this.hybrid.forms).reduce((acc, formKey) => {
+ if (!this.hybrid.forms[formKey]) {
+ acc[formKey] = null;
+ } else {
+ const data = this.hybrid.forms[formKey].toObject();
+ acc[formKey] = {
+ ...data,
+ system: {
+ ...data.system,
+ features: this.hybrid.forms[formKey].system.features.map(feature => ({
+ ...feature.toObject(),
+ uuid: feature.uuid,
+ selected: Boolean(this.hybrid.features?.[formKey]?.[feature.uuid])
+ })),
+ advantageOn: Object.keys(data.system.advantageOn).reduce((acc, key) => {
+ acc[key] = {
+ ...data.system.advantageOn[key],
+ selected: Boolean(this.hybrid.advantages?.[formKey]?.[key])
+ };
+ return acc;
+ }, {})
+ }
+ };
+ }
+ return acc;
+ }, {});
const maximumDragTier = Math.max(
this.selected?.system?.evolved?.maximumTier ?? 0,
@@ -83,13 +111,16 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
acc[tier.id].values[x.uuid] = {
selected: this.selected?.uuid == x.uuid,
value: x,
- draggable: maximumDragTier ? x.system.tier <= maximumDragTier : false
+ draggable:
+ !['evolved', 'hybrid'].includes(x.system.beastformType) && maximumDragTier
+ ? x.system.tier <= maximumDragTier
+ : false
};
return acc;
},
{}
- ); // Also get from compendium when added
+ );
context.canSubmit = this.canSubmit();
@@ -103,6 +134,19 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
return true;
case 'evolved':
return this.evolved.form;
+ case 'hybrid':
+ const selectedAdvantages = Object.values(this.hybrid.advantages).reduce(
+ (acc, form) => acc + Object.values(form).length,
+ 0
+ );
+ const selectedFeatures = Object.values(this.hybrid.features).reduce(
+ (acc, form) => acc + Object.values(form).length,
+ 0
+ );
+
+ const advantagesSelected = selectedAdvantages === this.selected.system.hybrid.advantages;
+ const featuresSelected = selectedFeatures === this.selected.system.hybrid.features;
+ return advantagesSelected && featuresSelected;
}
}
@@ -135,7 +179,62 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
if (this.selected) {
if (this.selected.system.beastformType !== 'evolved') this.evolved.form = null;
- if (this.selected.system.beastformType !== 'hybrid') this.hybrid = null;
+ if (this.selected.system.beastformType !== 'hybrid') {
+ this.hybrid.forms = {};
+ this.hybrid.advantages = {};
+ this.hybrid.features = {};
+ } else {
+ this.hybrid.forms = [...Array(this.selected.system.hybrid.beastformOptions).keys()].reduce((acc, _) => {
+ acc[foundry.utils.randomID()] = null;
+ return acc;
+ }, {});
+ }
+ }
+
+ this.render();
+ }
+
+ static toggleHybridFeature(_, button) {
+ const current = this.hybrid.features[button.dataset.form];
+ if (!current) this.hybrid.features[button.dataset.form] = {};
+
+ if (this.hybrid.features[button.dataset.form][button.id])
+ delete this.hybrid.features[button.dataset.form][button.id];
+ else {
+ const currentFeatures = Object.values(this.hybrid.features).reduce(
+ (acc, form) => acc + Object.values(form).length,
+ 0
+ );
+ if (currentFeatures === this.selected.system.hybrid.features) {
+ ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyFeatures'));
+ return;
+ }
+
+ const feature = this.hybrid.forms[button.dataset.form].system.features.find(x => x.uuid === button.id);
+ this.hybrid.features[button.dataset.form][button.id] = feature;
+ }
+
+ this.render();
+ }
+
+ static toggleHybridAdvantage(_, button) {
+ const current = this.hybrid.advantages[button.dataset.form];
+ if (!current) this.hybrid.advantages[button.dataset.form] = {};
+
+ if (this.hybrid.advantages[button.dataset.form][button.id])
+ delete this.hybrid.advantages[button.dataset.form][button.id];
+ else {
+ const currentAdvantages = Object.values(this.hybrid.advantages).reduce(
+ (acc, form) => acc + Object.values(form).length,
+ 0
+ );
+ if (currentAdvantages === this.selected.system.hybrid.advantages) {
+ ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyAdvantages'));
+ return;
+ }
+
+ const advantage = this.hybrid.forms[button.dataset.form].system.advantageOn[button.id];
+ this.hybrid.advantages[button.dataset.form][button.id] = advantage;
}
this.render();
@@ -164,23 +263,17 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
async _onDragStart(event) {
const target = event.currentTarget;
- if (!this.selected) {
- event.preventDefault();
- return;
- }
+ const abort = () => event.preventDefault();
+ if (!this.selected) abort();
const draggedForm = await foundry.utils.fromUuid(target.dataset.uuid);
+ if (['evolved', 'hybrid'].includes(draggedForm.system.beastformType)) abort();
+
if (this.selected.system.beastformType === 'evolved') {
- if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) {
- event.preventDefault();
- return;
- }
+ if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) abort();
}
if (this.selected.system.beastformType === 'hybrid') {
- if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) {
- event.preventDefault();
- return;
- }
+ if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) abort();
}
event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset));
@@ -191,9 +284,20 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
+ if (!item) return;
if (event.target.closest('.advanced-form-container.evolved')) {
this.evolved.form = item;
+ } else {
+ const hybridContainer = event.target.closest('.advanced-form-container.hybridized');
+ if (hybridContainer) {
+ const existingId = Object.keys(this.hybrid.forms).find(
+ key => this.hybrid.forms[key]?.uuid === item.uuid
+ );
+ if (existingId) this.hybrid.forms[existingId] = null;
+
+ this.hybrid.forms[hybridContainer.id] = item;
+ }
}
this.render();
diff --git a/module/applications/sheets/items/beastform.mjs b/module/applications/sheets/items/beastform.mjs
index bb77e35d..7e1656aa 100644
--- a/module/applications/sheets/items/beastform.mjs
+++ b/module/applications/sheets/items/beastform.mjs
@@ -36,7 +36,25 @@ export default class BeastformSheet extends DHBaseItemSheet {
const advantageOnInput = htmlElement.querySelector('.advantageon-input');
if (advantageOnInput) {
- const tagifyElement = new Tagify(advantageOnInput);
+ const tagifyElement = new Tagify(advantageOnInput, {
+ tagTextProp: 'name',
+ templates: {
+ tag(tagData) {
+ return `` : ''}
+