Fixed hybrid

This commit is contained in:
WBHarry 2025-07-19 00:47:08 +02:00
parent 011f5d2b14
commit 796cec01ee
13 changed files with 464 additions and 110 deletions

View file

@ -106,6 +106,10 @@
"horderHp": "Horde/HP" "horderHp": "Horde/HP"
}, },
"Character": { "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", "age": "Age",
"companionFeatures": "Companion Features", "companionFeatures": "Companion Features",
"contextMenu": { "contextMenu": {
@ -117,6 +121,10 @@
"unequip": "Unequip", "unequip": "Unequip",
"useItem": "Use Item" "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", "faith": "Faith",
"levelUp": "You can level up", "levelUp": "You can level up",
"pronouns": "Pronouns", "pronouns": "Pronouns",
@ -1014,6 +1022,7 @@
}, },
"Advantage": { "Advantage": {
"full": "Advantage", "full": "Advantage",
"plural": "Advantages",
"short": "Adv" "short": "Adv"
}, },
"Adversary": { "Adversary": {
@ -1169,6 +1178,11 @@
"hint": "The cost in stress you can pay to reduce minor damage to none." "hint": "The cost in stress you can pay to reduce minor damage to none."
} }
} }
},
"attack": {
"damage": {
"value": { "label": "Base Attack: Damage" }
}
} }
}, },
"Tabs": { "Tabs": {
@ -1266,6 +1280,7 @@
"title": "Title", "title": "Title",
"true": "True", "true": "True",
"type": "Type", "type": "Type",
"unarmedStrike": "Unarmed Strike",
"unarmored": "Unarmored", "unarmored": "Unarmored",
"use": "Use", "use": "Use",
"used": "Used", "used": "Used",
@ -1327,8 +1342,10 @@
"beastformEffect": "Beastform Transformation", "beastformEffect": "Beastform Transformation",
"evolve": "Evolve", "evolve": "Evolve",
"evolvedFeatureTitle": "Evolved", "evolvedFeatureTitle": "Evolved",
"evolvedDrag": "Drag a form here to evolve it.",
"hybridize": "Hybridize", "hybridize": "Hybridize",
"hybridFeatureTitle": "Hybrid Features" "hybridizeFeatureTitle": "Hybrid Features",
"hybridizeDrag": "Drag a form here to hybridize it."
}, },
"Class": { "Class": {
"hopeFeatures": "Hope Features", "hopeFeatures": "Hope Features",
@ -1573,7 +1590,9 @@
"featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.", "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.", "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.", "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": { "Tooltip": {
"openItemWorld": "Open Item World", "openItemWorld": "Open Item World",

View file

@ -7,7 +7,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
this.configData = configData; this.configData = configData;
this.selected = null; this.selected = null;
this.evolved = { form: null }; this.evolved = { form: null };
this.hybrid = null; this.hybrid = { forms: {}, advantages: {}, features: {} };
this._dragDrop = this._createDragDropHandlers(); this._dragDrop = this._createDragDropHandlers();
} }
@ -21,6 +21,8 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
}, },
actions: { actions: {
selectBeastform: this.selectBeastform, selectBeastform: this.selectBeastform,
toggleHybridFeature: this.toggleHybridFeature,
toggleHybridAdvantage: this.toggleHybridAdvantage,
submitBeastform: this.submitBeastform submitBeastform: this.submitBeastform
}, },
form: { form: {
@ -38,7 +40,7 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
/** @override */ /** @override */
static PARTS = { static PARTS = {
beastform: { 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.selectedBeastformEffect = this.selected?.effects?.find?.(x => x.type === 'beastform');
context.evolved = this.evolved; 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( const maximumDragTier = Math.max(
this.selected?.system?.evolved?.maximumTier ?? 0, this.selected?.system?.evolved?.maximumTier ?? 0,
@ -83,13 +111,16 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
acc[tier.id].values[x.uuid] = { acc[tier.id].values[x.uuid] = {
selected: this.selected?.uuid == x.uuid, selected: this.selected?.uuid == x.uuid,
value: x, value: x,
draggable: maximumDragTier ? x.system.tier <= maximumDragTier : false draggable:
!['evolved', 'hybrid'].includes(x.system.beastformType) && maximumDragTier
? x.system.tier <= maximumDragTier
: false
}; };
return acc; return acc;
}, },
{} {}
); // Also get from compendium when added );
context.canSubmit = this.canSubmit(); context.canSubmit = this.canSubmit();
@ -103,6 +134,19 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
return true; return true;
case 'evolved': case 'evolved':
return this.evolved.form; 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) {
if (this.selected.system.beastformType !== 'evolved') this.evolved.form = null; 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(); this.render();
@ -164,23 +263,17 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
async _onDragStart(event) { async _onDragStart(event) {
const target = event.currentTarget; const target = event.currentTarget;
if (!this.selected) { const abort = () => event.preventDefault();
event.preventDefault(); if (!this.selected) abort();
return;
}
const draggedForm = await foundry.utils.fromUuid(target.dataset.uuid); const draggedForm = await foundry.utils.fromUuid(target.dataset.uuid);
if (['evolved', 'hybrid'].includes(draggedForm.system.beastformType)) abort();
if (this.selected.system.beastformType === 'evolved') { if (this.selected.system.beastformType === 'evolved') {
if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) { if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) abort();
event.preventDefault();
return;
}
} }
if (this.selected.system.beastformType === 'hybrid') { if (this.selected.system.beastformType === 'hybrid') {
if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) { if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) abort();
event.preventDefault();
return;
}
} }
event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset)); event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset));
@ -191,9 +284,20 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
event.stopPropagation(); event.stopPropagation();
const data = foundry.applications.ux.TextEditor.getDragEventData(event); const data = foundry.applications.ux.TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid); const item = await fromUuid(data.uuid);
if (!item) return;
if (event.target.closest('.advanced-form-container.evolved')) { if (event.target.closest('.advanced-form-container.evolved')) {
this.evolved.form = item; 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(); this.render();

View file

@ -36,7 +36,25 @@ export default class BeastformSheet extends DHBaseItemSheet {
const advantageOnInput = htmlElement.querySelector('.advantageon-input'); const advantageOnInput = htmlElement.querySelector('.advantageon-input');
if (advantageOnInput) { if (advantageOnInput) {
const tagifyElement = new Tagify(advantageOnInput); const tagifyElement = new Tagify(advantageOnInput, {
tagTextProp: 'name',
templates: {
tag(tagData) {
return `<tag
contenteditable='false'
spellcheck='false'
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
${this.getAttributes(tagData)}>
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
<div>
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
</div>
</tag>`;
}
}
});
tagifyElement.on('add', this.advantageOnAdd.bind(this)); tagifyElement.on('add', this.advantageOnAdd.bind(this));
tagifyElement.on('remove', this.advantageOnRemove.bind(this)); tagifyElement.on('remove', this.advantageOnRemove.bind(this));
} }
@ -61,19 +79,25 @@ export default class BeastformSheet extends DHBaseItemSheet {
return data; return data;
}); });
context.advantageOn = JSON.stringify(
Object.keys(context.document.system.advantageOn).map(key => ({
value: key,
name: context.document.system.advantageOn[key].value
}))
);
return context; return context;
} }
async advantageOnAdd(event) { async advantageOnAdd(event) {
await this.document.update({ await this.document.update({
'system.advantageOn': [...this.document.system.advantageOn, event.detail.data.value] [`system.advantageOn.${foundry.utils.randomID()}`]: { value: event.detail.data.value }
}); });
} }
async advantageOnRemove(event) { async advantageOnRemove(event) {
await this.document.update({ await this.document.update({
'system.advantageOn': this.document.system.advantageOn.filter(x => x !== event.detail.data.value) [`system.advantageOn.-=${event.detail.data.value}`]: null
}); });
} }
} }

View file

@ -48,19 +48,29 @@ export default class DhBeastformAction extends DHBaseAction {
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)]; formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
} }
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
Object.keys(formCategory).forEach(advantageKey => {
advantages[advantageKey] = formCategory[advantageKey];
});
return advantages;
}, {});
formData.system.features = [
...formData.system.features,
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
];
}
this.actor.createEmbeddedDocuments('Item', [formData]); this.actor.createEmbeddedDocuments('Item', [formData]);
} }
async handleActiveTransformations() { async handleActiveTransformations() {
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform'); const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
if (beastformEffects.length > 0) { const existingEffects = beastformEffects.length > 0;
for (let effect of beastformEffects) { await this.actor.deleteEmbeddedDocuments(
await effect.delete(); 'ActiveEffect',
} beastformEffects.map(x => x.id)
);
return true; return existingEffects;
}
return false;
} }
} }

View file

@ -32,7 +32,7 @@ export default class DHDamageAction extends DHBaseAction {
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData)); if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData));
const config = { const config = {
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }), title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
roll: { formula }, roll: { formula },
targets: systemData.targets.filter(t => t.hit) ?? data.targets, targets: systemData.targets.filter(t => t.hit) ?? data.targets,
hasSave: this.hasSave, hasSave: this.hasSave,

View file

@ -3,6 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs'; import DhLevelData from '../levelData.mjs';
import BaseDataActor from './base.mjs'; import BaseDataActor from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
import ActionField from '../fields/actionField.mjs';
export default class DhCharacter extends BaseDataActor { export default class DhCharacter extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character']; static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
@ -87,8 +88,45 @@ export default class DhCharacter extends BaseDataActor {
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
}), }),
advantageSources: new fields.ArrayField(new fields.StringField()), attack: new ActionField({
disadvantageSources: new fields.ArrayField(new fields.StringField()), initial: {
name: 'DAGGERHEART.GENERAL.unarmedStrike',
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
_id: foundry.utils.randomID(),
systemPath: 'attack',
type: 'attack',
range: 'melee',
target: {
type: 'any',
amount: 1
},
roll: {
type: 'attack',
trait: 'strength'
},
damage: {
parts: [
{
type: ['physical'],
value: {
custom: {
enabled: true,
formula: '@system.rules.attack.damage.value'
}
}
}
]
}
}
}),
advantageSources: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
}),
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
}),
levelData: new fields.EmbeddedDataField(DhLevelData), levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({ bonuses: new fields.SchemaField({
roll: new fields.SchemaField({ roll: new fields.SchemaField({
@ -161,6 +199,15 @@ export default class DhCharacter extends BaseDataActor {
magical: new fields.BooleanField({ initial: false }), magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false }) physical: new fields.BooleanField({ initial: false })
}), }),
attack: new fields.SchemaField({
damage: new fields.SchemaField({
value: new fields.StringField({
required: true,
initial: '@profd4',
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.value.label'
})
})
}),
weapon: new fields.SchemaField({ weapon: new fields.SchemaField({
/* Unimplemented /* Unimplemented
-> Should remove the lowest damage dice from weapon damage -> Should remove the lowest damage dice from weapon damage
@ -420,11 +467,14 @@ export default class DhCharacter extends BaseDataActor {
const data = super.getRollData(); const data = super.getRollData();
return { return {
...data, ...data,
system: {
...this.resources.tokens, ...this.resources.tokens,
...this.resources.dice, ...this.resources.dice,
...this.bonuses, ...this.bonuses,
...this.rules,
tier: this.tier, tier: this.tier,
level: this.levelData.level.current level: this.levelData.level.current
}
}; };
} }

View file

@ -50,7 +50,11 @@ export default class DHBeastform extends BaseDataItem {
initial: CONFIG.DH.ACTOR.abilities.agility.id initial: CONFIG.DH.ACTOR.abilities.agility.id
}), }),
examples: new fields.StringField(), examples: new fields.StringField(),
advantageOn: new fields.ArrayField(new fields.StringField()), advantageOn: new fields.TypedObjectField(
new fields.SchemaField({
value: new fields.StringField()
})
),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }), features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
evolved: new fields.SchemaField({ evolved: new fields.SchemaField({
maximumTier: new fields.NumberField({ maximumTier: new fields.NumberField({
@ -104,7 +108,13 @@ export default class DHBeastform extends BaseDataItem {
await beastformEffect.updateSource({ await beastformEffect.updateSource({
changes: [ changes: [
...beastformEffect.changes, ...beastformEffect.changes,
{ key: 'system.advantageSources', mode: 2, value: this.advantageOn.join(', ') } {
key: 'system.advantageSources',
mode: 2,
value: Object.values(this.advantageOn)
.map(x => x.value)
.join(', ')
}
], ],
system: { system: {
characterTokenData: { characterTokenData: {

View file

@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument {
}); });
bars.sort((a, b) => a.label.compare(b.label)); bars.sort((a, b) => a.label.compare(b.label));
const invalidAttributes = ['gold', 'levelData', 'rules.damageReduction.maxArmorMarked.value']; const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value'];
const values = attributes.value.reduce((acc, v) => { const values = attributes.value.reduce((acc, v) => {
const a = v.join('.'); const a = v.join('.');
if (invalidAttributes.some(x => a.startsWith(x))) return acc; if (invalidAttributes.some(x => a.startsWith(x))) return acc;
@ -33,18 +33,20 @@ export default class DHToken extends TokenDocument {
return bars.concat(values); return bars.concat(values);
} }
static _getTrackedAttributesFromSchema(schema, _path=[]) { static _getTrackedAttributesFromSchema(schema, _path = []) {
const attributes = {bar: [], value: []}; const attributes = { bar: [], value: [] };
for ( const [name, field] of Object.entries(schema.fields) ) { for (const [name, field] of Object.entries(schema.fields)) {
const p = _path.concat([name]); const p = _path.concat([name]);
if ( field instanceof foundry.data.fields.NumberField ) attributes.value.push(p); if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p);
if ( field instanceof foundry.data.fields.ArrayField ) attributes.value.push(p); if (field instanceof foundry.data.fields.StringField) attributes.value.push(p);
if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p);
const isSchema = field instanceof foundry.data.fields.SchemaField; const isSchema = field instanceof foundry.data.fields.SchemaField;
const isModel = field instanceof foundry.data.fields.EmbeddedDataField; const isModel = field instanceof foundry.data.fields.EmbeddedDataField;
if ( isSchema || isModel ) {
if (isSchema || isModel) {
const schema = isModel ? field.model.schema : field; const schema = isModel ? field.model.schema : field;
const isBar = schema.has && schema.has("value") && schema.has("max"); const isBar = schema.has && schema.has('value') && schema.has('max');
if ( isBar ) attributes.bar.push(p); if (isBar) attributes.bar.push(p);
else { else {
const inner = this.getTrackedAttributes(schema, p); const inner = this.getTrackedAttributes(schema, p);
attributes.bar.push(...inner.bar); attributes.bar.push(...inner.bar);

View file

@ -1,13 +1,26 @@
@import '../../utils/colors.less'; @import '../../utils/colors.less';
@import '../../utils/mixin.less'; @import '../../utils/mixin.less';
.appTheme({ .theme-light .application.daggerheart.dh-style.views.beastform-selection .beastforms-outer-container {
&.beastform-selection { .beastform-title {
.beastforms-outer-container .beastform-title { background-image: url('../assets/parchments/dh-parchment-light.png');
}
.advanced-container {
.advanced-forms-container {
.advanced-form-container {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
.hybrid-data-wrapper .hybrid-data-container .hybrid-data-inner-container .hybrid-data {
background-image: url('../assets/parchments/dh-parchment-light.png'); background-image: url('../assets/parchments/dh-parchment-light.png');
} }
} }
}, {}); .form-features .form-feature {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
}
}
.application.daggerheart.dh-style.views.beastform-selection { .application.daggerheart.dh-style.views.beastform-selection {
.beastforms-outer-container { .beastforms-outer-container {
@ -80,6 +93,17 @@
width: 300px; width: 300px;
} }
h2 {
margin: 0;
}
.advanced-forms-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 16px;
}
.advanced-form-container { .advanced-form-container {
position: relative; position: relative;
display: flex; display: flex;
@ -91,11 +115,72 @@
height: 120px; height: 120px;
align-items: center; align-items: center;
text-align: center; text-align: center;
color: light-dark(@dark, @beige);
background-image: url('../assets/parchments/dh-parchment-dark.png');
&.hybridized {
flex-direction: column;
justify-content: start;
padding-top: 4px;
height: 200px;
overflow: hidden;
&.empty {
justify-content: center;
}
.beastform-title {
position: initial;
}
}
.empty-form { .empty-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
i {
font-size: 24px;
}
}
.beastform-title-wrapper {
height: 44px;
}
.hybrid-data-wrapper {
overflow: auto;
.hybrid-data-container {
display: flex;
flex-direction: column;
gap: 2px;
padding: 0 4px;
label {
font-weight: bold;
}
.hybrid-data-inner-container {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 4px;
.hybrid-data {
padding: 0 2px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
color: light-dark(@dark, @beige);
background-image: url('../assets/parchments/dh-parchment-dark.png');
opacity: 0.4;
&.active {
opacity: 1;
}
}
}
}
} }
} }
@ -104,13 +189,13 @@
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
padding: 0 16px; padding: 0 16px;
margin-top: 8px; margin: 8px 0;
.form-feature { .form-feature {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
padding: 0 2px; padding: 0 4px;
border: 1px solid light-dark(@dark-blue, @golden); border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px; border-radius: 6px;
color: light-dark(@dark, @beige); color: light-dark(@dark, @beige);

View file

@ -0,0 +1,94 @@
<div>
<div class="beastforms-outer-container">
<div class="beastforms-container">
{{#each beastformTiers as |tier tierKey|}}
<fieldset class="beastforms-tier">
<legend>{{tier.label}}</legend>
{{#each tier.values as |form uuid|}}
<div data-action="selectBeastform" data-uuid="{{uuid}}" data-tooltip="{{concat "#item#" uuid}}" class="beastform-container {{#unless form.selected}}inactive{{/unless}} {{#if form.draggable}}draggable{{/if}}">
<img src="{{form.value.img}}" />
<div class="beastform-title">{{form.value.name}}</div>
</div>
{{/each}}
</fieldset>
{{/each}}
</div>
<div class="advanced-container {{#if (or (eq selected.system.beastformType 'evolved') (eq selected.system.beastformType 'hybrid'))}}expanded{{/if}}">
{{#if (eq selected.system.beastformType 'evolved')}}
<h2>{{localize "DAGGERHEART.ITEMS.Beastform.evolve"}}</h2>
<div class="form-features">
{{#if selectedBeastformEffect}}
<div class="form-feature" data-tooltip="{{concat "#item#" selectedBeastformEffect.uuid}}">
<h4>{{localize "DAGGERHEART.ITEMS.Beastform.evolvedFeatureTitle"}}</h4>
<div>{{{selectedBeastformEffect.description}}}</div>
</div>
{{/if}}
</div>
<div class="advanced-form-container evolved">
{{#if evolved.form}}
<div class="beastform-title">{{concat (localize "DAGGERHEART.CONFIG.BeastformType.evolved") " " evolved.form.name}}</div>
<img src="{{evolved.form.img}}" />
{{else}}
<div class="empty-form">
<i class="fa-solid fa-plus"></i>
<label>{{localize "DAGGERHEART.ITEMS.Beastform.evolvedDrag"}}</label>
</div>
{{/if}}
</div>
{{/if}}
{{#if (eq selected.system.beastformType 'hybrid')}}
<h2>{{localize "DAGGERHEART.ITEMS.Beastform.hybridize"}}</h2>
<div class="form-features">
{{#if selectedBeastformEffect}}
<div class="form-feature" data-tooltip="{{concat "#item#" selectedBeastformEffect.uuid}}">
<h4>{{localize "DAGGERHEART.ITEMS.Beastform.hybridizeFeatureTitle"}}</h4>
<div>{{{selectedBeastformEffect.description}}}</div>
</div>
{{/if}}
</div>
<div class="advanced-forms-container">
{{#each hybridForms as | form key |}}
<div class="advanced-form-container hybridized {{#unless form}}empty{{/unless}}" id="{{key}}">
{{#if form}}
<div class="beastform-title-wrapper">
<div class="beastform-title">{{form.name}}</div>
</div>
<div class="hybrid-data-wrapper">
<div class="hybrid-data-container">
<label>{{localize "DAGGERHEART.GENERAL.features"}}</label>
<div class="hybrid-data-inner-container">
{{#each form.system.features as | feature |}}
<a data-action="toggleHybridFeature" id="{{feature.uuid}}" data-form="{{@../key}}"><div class="hybrid-data {{#if feature.selected}}active{{/if}}" data-tooltip="{{concat "#item#" feature.uuid}}">{{feature.name}}</div></a>
{{/each}}
</div>
</div>
<div class="hybrid-data-container">
<label>{{localize "DAGGERHEART.GENERAL.Advantage.plural"}}</label>
<div class="hybrid-data-inner-container">
{{#each form.system.advantageOn as | advantage id |}}
<a data-action="toggleHybridAdvantage" id="{{id}}" data-form="{{@../key}}"><div class="hybrid-data {{#if advantage.selected}}active{{/if}}">{{advantage.value}}</div></a>
{{/each}}
</div>
</div>
</div>
{{else}}
<div class="empty-form">
<i class="fa-solid fa-plus"></i>
<label>{{localize "DAGGERHEART.ITEMS.Beastform.hybridizeDrag"}}</label>
</div>
{{/if}}
</div>
{{/each}}
</div>
{{/if}}
</div>
</div>
<footer>
<button type="button" data-action="submitBeastform" {{#if (not canSubmit)}}disabled{{/if}}>{{localize "DAGGERHEART.ITEMS.Beastform.transform"}}</button>
</footer>
</div>

View file

@ -1,45 +0,0 @@
<div>
<div class="beastforms-outer-container">
<div class="beastforms-container">
{{#each beastformTiers as |tier tierKey|}}
<fieldset class="beastforms-tier">
<legend>{{tier.label}}</legend>
{{#each tier.values as |form uuid|}}
<div data-action="selectBeastform" data-uuid="{{uuid}}" data-tooltip="{{concat "#item#" uuid}}" class="beastform-container {{#unless form.selected}}inactive{{/unless}} {{#if form.draggable}}draggable{{/if}}">
<img src="{{form.value.img}}" />
<div class="beastform-title">{{form.value.name}}</div>
</div>
{{/each}}
</fieldset>
{{/each}}
</div>
<div class="advanced-container {{#if (or (eq selected.system.beastformType 'evolved') (eq selected.system.beastformType 'hybrid'))}}expanded{{/if}}">
{{#if (eq selected.system.beastformType 'evolved')}}
<h2>{{localize "DAGGERHEART.ITEMS.Beastform.evolve"}}</h2>
<div class="advanced-form-container evolved">
{{#if evolved.form}}
<div class="beastform-title">{{concat (localize "DAGGERHEART.CONFIG.BeastformType.evolved") " " evolved.form.name}}</div>
<img src="{{evolved.form.img}}" />
{{else}}
<div class="empty-form">
<i class="fa-solid fa-plus"></i>
<label>{{localize "Drag a form here to evolve it"}}</label>
</div>
{{/if}}
</div>
<div class="form-features">
{{#if selectedBeastformEffect}}
<div class="form-feature" data-tooltip="{{concat "#item#" selectedBeastformEffect.uuid}}">
<h4>{{localize "DAGGERHEART.ITEMS.Beastform.evolvedFeatureTitle"}}</h4>
<div>{{{selectedBeastformEffect.description}}}</div>
</div>
{{/if}}
</div>
{{/if}}
</div>
</div>
<footer>
<button type="button" data-action="submitBeastform" {{#if (not canSubmit)}}disabled{{/if}}>{{localize "DAGGERHEART.ITEMS.Beastform.transform"}}</button>
</footer>
</div>

View file

@ -6,7 +6,7 @@
</div> </div>
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
s {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}} {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
<footer class="form-footer"> <footer class="form-footer">
<button data-action="reset"> <button data-action="reset">

View file

@ -11,12 +11,13 @@
{{formGroup systemFields.mainTrait value=source.system.mainTrait blank=false localize=true}} {{formGroup systemFields.mainTrait value=source.system.mainTrait blank=false localize=true}}
</div> </div>
{{#unless (eq source.system.beastformType 'hybrid')}}
{{formGroup systemFields.examples value=source.system.examples localize=true}} {{formGroup systemFields.examples value=source.system.examples localize=true}}
<div class="advantage-on-section"> <div class="advantage-on-section">
<label>{{localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label"}}</label> <label>{{localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label"}}</label>
<input class="advantageon-input" value="{{source.system.advantageOn}}" /> <input class="advantageon-input" value="{{advantageOn}}" />
</div> </div>
{{/unless}}
{{/if}} {{/if}}
<fieldset class="two-columns even"> <fieldset class="two-columns even">