Character Setup Rework (#315)

* Updated to make use of setup tabs. Ancestry now has primary/secondary features

* Changed so ancestry uses a single Features field

* Revert "Changed so ancestry uses a single Features field"

This reverts commit 0bda6b5dbe.

* Reapply "Changed so ancestry uses a single Features field"

This reverts commit 1febafd441.

* Made it work again the bad way \._./

* Changed so that Feature(Item) has a primary field

* Feature(Item) now has subtype instead of primary as a field

* Fixed experience/evasion

* Moved light styling to appTheme mixing

* Added svg and style rules
This commit is contained in:
WBHarry 2025-07-14 01:58:16 +02:00 committed by GitHub
parent 4be3e6179c
commit a768b1dfdb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 756 additions and 144 deletions

View file

@ -162,12 +162,25 @@
}, },
"APPLICATIONS": { "APPLICATIONS": {
"CharacterCreation": { "CharacterCreation": {
"setupTabs": {
"ancestry": "Ancestry",
"community": "Community",
"class": "Class",
"experience": "Experience",
"traits": "Traits",
"domainCards": "Domain Cards"
},
"ancestryNamePlaceholder": "Your ancestry's name",
"buttonTitle": "Character Setup",
"choice": "Choice", "choice": "Choice",
"finishCreation": "Finish Character Setup", "finishCreation": "Finish Character Setup",
"heritage": "Heritage", "heritage": "Heritage",
"initialExperiences": "Initial Experiences", "initialExperiences": "Initial Experiences",
"mixedAncestry": "Mixed Ancestry",
"newExperience": "New Experience..", "newExperience": "New Experience..",
"selectAncestry": "Select Ancestry", "selectAncestry": "Select Ancestry",
"selectPrimaryAncestry": "Select Primary Ancestry",
"selectSecondaryAncestry": "Select Secondary Ancestry",
"selectArmor": "Select Armor", "selectArmor": "Select Armor",
"selectClass": "Select Class", "selectClass": "Select Class",
"selectCommunity": "Select Community", "selectCommunity": "Select Community",
@ -1177,6 +1190,10 @@
"value": { "label": "Value" } "value": { "label": "Value" }
} }
}, },
"Ancestry": {
"primaryFeature": "Primary Feature",
"secondaryFeature": "Secondary Feature"
},
"Armor": { "Armor": {
"baseScore": "Base Score", "baseScore": "Base Score",
"baseThresholds": { "baseThresholds": {
@ -1432,7 +1449,8 @@
"damageAlreadyNone": "The damage has already been reduced to none", "damageAlreadyNone": "The damage has already been reduced to none",
"noAvailableArmorMarks": "You have no more available armor marks", "noAvailableArmorMarks": "You have no more available armor marks",
"notEnoughStress": "You don't have enough stress", "notEnoughStress": "You don't have enough stress",
"damageIgnore": "{character} did not take damage" "damageIgnore": "{character} did not take damage",
"featureIsMissing": "Feature is missing"
}, },
"Tooltip": { "Tooltip": {
"openItemWorld": "Open Item World", "openItemWorld": "Open Item World",

View file

@ -11,13 +11,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.setup = { this.setup = {
traits: this.character.system.traits, traits: this.character.system.traits,
ancestry: this.character.system.ancestry ?? {}, ancestryName: '',
mixedAncestry: false,
primaryAncestry: this.character.system.ancestry ?? {},
secondaryAncestry: {},
community: this.character.system.community ?? {}, community: this.character.system.community ?? {},
class: this.character.system.class?.value ?? {}, class: this.character.system.class?.value ?? {},
subclass: this.character.system.class?.subclass ?? {}, subclass: this.character.system.class?.subclass ?? {},
experiences: { experiences: {
[foundry.utils.randomID()]: { description: '', value: 2 }, [foundry.utils.randomID()]: { name: '', value: 2 },
[foundry.utils.randomID()]: { description: '', value: 2 } [foundry.utils.randomID()]: { name: '', value: 2 }
}, },
domainCards: { domainCards: {
[foundry.utils.randomID()]: {}, [foundry.utils.randomID()]: {},
@ -47,12 +50,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'], classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
position: { width: 800, height: 'auto' }, position: { width: 700, height: 'auto' },
actions: { actions: {
viewCompendium: this.viewCompendium, viewCompendium: this.viewCompendium,
viewItem: this.viewItem, viewItem: this.viewItem,
useSuggestedTraits: this.useSuggestedTraits, useSuggestedTraits: this.useSuggestedTraits,
equipmentChoice: this.equipmentChoice, equipmentChoice: this.equipmentChoice,
setupGoNext: this.setupGoNext,
finish: this.finish finish: this.finish
}, },
form: { form: {
@ -76,6 +80,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static PARTS = { static PARTS = {
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' }, tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.hbs' },
setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' }, setup: { template: 'systems/daggerheart/templates/characterCreation/tabs/setup.hbs' },
ancestry: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/ancestry.hbs' },
community: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/community.hbs' },
class: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/class.hbs' },
traits: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/traits.hbs' },
experience: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/experience.hbs' },
domainCards: { template: 'systems/daggerheart/templates/characterCreation/setupTabs/domainCards.hbs' },
equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' }, equipment: { template: 'systems/daggerheart/templates/characterCreation/tabs/equipment.hbs' },
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' }, // story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.hbs' },
footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' } footer: { template: 'systems/daggerheart/templates/characterCreation/footer.hbs' }
@ -107,6 +117,51 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
// } // }
}; };
static SETUPTABS = {
ancestry: {
active: true,
cssClass: '',
group: 'setup',
id: 'ancestry',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.ancestry'
},
community: {
active: false,
cssClass: '',
group: 'setup',
id: 'community',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.community'
},
class: {
active: false,
cssClass: '',
group: 'setup',
id: 'class',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.class'
},
traits: {
active: false,
cssClass: '',
group: 'setup',
id: 'traits',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.traits'
},
experience: {
active: false,
cssClass: '',
group: 'setup',
id: 'experience',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.experience'
},
domainCards: {
active: false,
cssClass: '',
group: 'setup',
id: 'domainCards',
label: 'DAGGERHEART.APPLICATIONS.CharacterCreation.setupTabs.domainCards'
}
};
_getTabs(tabs) { _getTabs(tabs) {
for (const v of Object.values(tabs)) { for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active; v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
@ -114,14 +169,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
switch (v.id) { switch (v.id) {
case 'setup': case 'setup':
const ancestryFinished = this.setup.primaryAncestry.uuid;
const communityFinished = this.setup.community.uuid;
const classFinished = this.setup.class.uuid && this.setup.subclass.uuid; const classFinished = this.setup.class.uuid && this.setup.subclass.uuid;
const heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid;
const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null); const traitsFinished = Object.values(this.setup.traits).every(x => x.value !== null);
const experiencesFinished = Object.values(this.setup.experiences).every(x => x.description); const experiencesFinished = Object.values(this.setup.experiences).every(x => x.name);
const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid); const domainCardsFinished = Object.values(this.setup.domainCards).every(x => x.uuid);
v.finished = v.finished =
ancestryFinished &&
communityFinished &&
classFinished && classFinished &&
heritageFinished &&
traitsFinished && traitsFinished &&
experiencesFinished && experiencesFinished &&
domainCardsFinished; domainCardsFinished;
@ -146,9 +203,37 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
return tabs; return tabs;
} }
_getSetupTabs(tabs) {
for (const v of Object.values(tabs)) {
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
v.cssClass = v.active ? 'active' : '';
switch (v.id) {
case 'community':
v.disabled = this.setup.visibility < 2;
break;
case 'class':
v.disabled = this.setup.visibility < 3;
break;
case 'traits':
v.disabled = this.setup.visibility < 4;
break;
case 'experience':
v.disabled = this.setup.visibility < 5;
break;
case 'domainCards':
v.disabled = this.setup.visibility < 6;
break;
}
}
return tabs;
}
changeTab(tab, group, options) { changeTab(tab, group, options) {
super.changeTab(tab, group, options); super.changeTab(tab, group, options);
if (group === 'primary') {
for (var listTab of Object.keys(this.constructor.TABS)) { for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`); const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) { if (listTab === tab) {
@ -158,11 +243,17 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
} }
} }
} }
}
_attachPartListeners(partId, htmlElement, options) { _attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options); super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement)); this._dragDrop.forEach(d => d.bind(htmlElement));
htmlElement.querySelectorAll('.mixed-ancestry-slider').forEach(element => {
element.addEventListener('input', this.mixedAncestryToggle.bind(this));
element.addEventListener('click', this.mixedAncestryToggle.bind(this));
});
} }
async _prepareContext(_options) { async _prepareContext(_options) {
@ -174,7 +265,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _preparePartContext(partId, context) { async _preparePartContext(partId, context) {
switch (partId) { switch (partId) {
case 'footer':
context.isLastTab = this.tabGroups.setup === 'domainCards';
switch (this.tabGroups.setup) {
case null:
case 'ancestry':
context.nextDisabled = this.setup.visibility === 1;
break;
case 'community':
context.nextDisabled = this.setup.visibility === 2;
break;
case 'class':
context.nextDisabled = this.setup.visibility === 3;
break;
case 'traits':
context.nextDisabled = this.setup.visibility === 4;
break;
case 'experience':
context.nextDisabled = this.setup.visibility === 5;
break;
}
break;
case 'setup': case 'setup':
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
const availableTraitModifiers = game.settings const availableTraitModifiers = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew) .get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait })); .traitArray.map(trait => ({ key: trait, name: trait }));
@ -215,13 +329,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
context.experience = { context.experience = {
values: this.setup.experiences, values: this.setup.experiences,
nrTotal: Object.keys(this.setup.experiences).length, nrTotal: Object.keys(this.setup.experiences).length,
nrSelected: Object.values(this.setup.experiences).reduce( nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 1 : 0), 0)
(acc, exp) => acc + (exp.description ? 1 : 0),
0
)
}; };
context.ancestry = { ...this.setup.ancestry, compendium: 'ancestries' }; context.mixedAncestry = Number(this.setup.mixedAncestry);
context.ancestryName = this.setup.ancestryName;
context.primaryAncestry = { ...this.setup.primaryAncestry, compendium: 'ancestries' };
context.secondaryAncestry = { ...this.setup.secondaryAncestry, compendium: 'ancestries' };
context.community = { ...this.setup.community, compendium: 'communities' }; context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' }; context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' }; context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
@ -278,18 +392,29 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.render(); this.render();
} }
mixedAncestryToggle(event) {
event.preventDefault();
event.stopPropagation();
this.setup.mixedAncestry = !this.setup.mixedAncestry;
if (!this.setup.mixedAncestry) this.setup.secondaryAncestry = {};
this.render();
}
getUpdateVisibility() { getUpdateVisibility() {
switch (this.setup.visibility) { switch (this.setup.visibility) {
case 6:
return 6;
case 5: case 5:
return 5; return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
case 4: case 4:
return Object.values(this.setup.experiences).every(x => x.description) ? 5 : 4; return Object.values(this.setup.traits).every(x => x.value !== null) ? 5 : 4;
case 3: case 3:
return Object.values(this.setup.traits).every(x => x.value !== null) ? 4 : 3; return this.setup.class.uuid && this.setup.subclass.uuid ? 4 : 3;
case 2: case 2:
return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2; return this.setup.community.uuid ? 3 : 2;
case 1: case 1:
return this.setup.class.uuid && this.setup.subclass.uuid ? 2 : 1; return this.setup.primaryAncestry.uuid ? 2 : 1;
} }
} }
@ -348,8 +473,46 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.render(); this.render();
} }
static setupGoNext() {
switch (this.setup.visibility) {
case 2:
this.tabGroups.setup = 'community';
break;
case 3:
this.tabGroups.setup = 'class';
break;
case 4:
this.tabGroups.setup = 'traits';
break;
case 5:
this.tabGroups.setup = 'experience';
break;
case 6:
this.tabGroups.setup = 'domainCards';
break;
}
this.render();
}
static async finish() { static async finish() {
await this.character.createEmbeddedDocuments('Item', [this.setup.ancestry]); const primaryAncestryFeature = this.setup.primaryAncestry.system.primaryFeature;
const secondaryAncestryFeature = this.setup.secondaryAncestry?.uuid
? this.setup.secondaryAncestry.system.secondaryFeature
: this.setup.primaryAncestry.system.secondaryFeature;
const ancestry = {
...this.setup.primaryAncestry,
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name,
system: {
...this.setup.primaryAncestry.system,
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid],
primaryFeature: primaryAncestryFeature.uuid,
secondaryFeature: secondaryAncestryFeature.uuid
}
};
await this.character.createEmbeddedDocuments('Item', [ancestry]);
await this.character.createEmbeddedDocuments('Item', [this.setup.community]); await this.character.createEmbeddedDocuments('Item', [this.setup.community]);
await this.character.createEmbeddedDocuments('Item', [this.setup.class]); await this.character.createEmbeddedDocuments('Item', [this.setup.class]);
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]); await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
@ -396,8 +559,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _onDrop(event) { async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event); const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid); const item = await foundry.utils.fromUuid(data.uuid);
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) { if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
this.setup.ancestry = { this.setup.ancestryName = item.name;
this.setup.primaryAncestry = {
...item,
effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid
};
} else if (item.type === 'ancestry' && event.target.closest('.secondary-ancestry-card')) {
this.setup.secondaryAncestry = {
...item, ...item,
effects: Array.from(item.effects).map(x => x.toObject()), effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid uuid: item.uuid

View file

@ -181,7 +181,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
const target = button.closest('.feature-item'); const target = button.closest('.feature-item');
const feature = this.document.system.features.find(x => x?.id === target.id); const feature = this.document.system.features.find(x => x?.id === target.id);
if (!feature) { if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return; return;
} }
@ -251,7 +251,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (featureItem) { if (featureItem) {
const feature = this.document.system.features.find(x => x?.id === featureItem.id); const feature = this.document.system.features.find(x => x?.id === featureItem.id);
if (!feature) { if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return; return;
} }

View file

@ -10,10 +10,6 @@ export default class DHHeritageSheet extends DHBaseItemSheet {
static PARTS = { static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' }, description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
feature: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.feature']
},
effects: { effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs', template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects'] scrollable: ['.effects']

View file

@ -3,12 +3,109 @@ import DHHeritageSheet from '../api/heritage-sheet.mjs';
export default class AncestrySheet extends DHHeritageSheet { export default class AncestrySheet extends DHHeritageSheet {
/**@inheritdoc */ /**@inheritdoc */
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['ancestry'] classes: ['ancestry'],
actions: {
addFeature: AncestrySheet.#addFeature,
editFeature: AncestrySheet.#editFeature,
removeFeature: AncestrySheet.#removeFeature
},
dragDrop: [{ dragSelector: null, dropSelector: '.tab.features .drop-section' }]
}; };
/**@inheritdoc */ /**@inheritdoc */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/ancestry/header.hbs' },
...super.PARTS ...super.PARTS,
features: { template: 'systems/daggerheart/templates/sheets/items/ancestry/features.hbs' }
}; };
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Add a new feature to the item, prompting the user for its type.
* @type {ApplicationClickAction}
*/
static async #addFeature(_event, button) {
const feature = await game.items.documentClass.create({
type: 'feature',
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }),
system: {
subType: button.dataset.type
}
});
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), feature.uuid]
});
}
/**
* Edit an existing feature on the item
* @type {ApplicationClickAction}
*/
static async #editFeature(_event, button) {
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
if (!feature || Object.keys(feature).length === 0) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}
feature.sheet.render(true);
}
/**
* Remove a feature from the item.
* @type {ApplicationClickAction}
*/
static async #removeFeature(event, button) {
event.stopPropagation();
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
const featureExists = feature && Object.keys(feature).length > 0;
if (featureExists) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.Item.feature`),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
}
if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null });
await this.document.update({
'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid)
});
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
/**
* On drop on the item.
* @param {DragEvent} event - The drag event
*/
async _onDrop(event) {
event.stopPropagation();
event.preventDefault();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary';
await item.update({ 'system.subType': subType });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
}
}
} }

View file

@ -200,7 +200,7 @@ export default class ClassSheet extends DHBaseItemSheet {
const actionPath = this.getActionPath(button.dataset.type); const actionPath = this.getActionPath(button.dataset.type);
const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId); const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId);
if (!feature) { if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return; return;
} }

View file

@ -9,6 +9,10 @@ export default class CommunitySheet extends DHHeritageSheet {
/**@inheritdoc */ /**@inheritdoc */
static PARTS = { static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/items/community/header.hbs' },
...super.PARTS ...super.PARTS,
feature: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.feature']
}
}; };
} }

View file

@ -50,7 +50,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
static async editFeature(_, button) { static async editFeature(_, button) {
const feature = this.document.system[button.dataset.type]; const feature = this.document.system[button.dataset.type];
if (!feature) { if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return; return;
} }
@ -71,7 +71,7 @@ export default class SubclassSheet extends DHBaseItemSheet {
if (featureItem) { if (featureItem) {
const feature = this.document.system[featureItem.dataset.type]; const feature = this.document.system[featureItem.dataset.type];
if (!feature) { if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing')); ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return; return;
} }

View file

@ -1320,6 +1320,11 @@ export const featureTypes = {
} }
}; };
export const featureSubTypes = {
primary: 'primary',
secondary: 'secondary'
};
export const actionTypes = { export const actionTypes = {
passive: { passive: {
id: 'passive', id: 'passive',

View file

@ -18,4 +18,20 @@ export default class DHAncestry extends BaseDataItem {
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }) features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
}; };
} }
get primaryFeature() {
return (
this.features.find(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.primary) ??
(this.features.filter(x => !x).length > 0 ? {} : null)
);
}
get secondaryFeature() {
return (
this.features.find(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.secondary) ??
(this.features.filter(x => !x || x.system.subType === CONFIG.DH.ITEM.featureSubTypes.primary).length > 1
? {}
: null)
);
}
} }

View file

@ -22,6 +22,7 @@ export default class DHFeature extends BaseDataItem {
nullable: true, nullable: true,
initial: null initial: null
}), }),
subType: new fields.StringField({ choices: CONFIG.DH.ITEM.featureSubTypes, nullable: true, initial: null }),
originId: new fields.StringField({ nullable: true, initial: null }), originId: new fields.StringField({ nullable: true, initial: null }),
identifier: new fields.StringField(), identifier: new fields.StringField(),
actions: new fields.ArrayField(new ActionField()) actions: new fields.ArrayField(new ActionField())

View file

@ -1,9 +1,8 @@
import DamageSelectionDialog from '../applications/dialogs/damageSelectionDialog.mjs'; import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import { emitAsGM, emitAsOwner, GMUpdateEvent, socketEvent } from '../systemRegistration/socket.mjs';
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs'; import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
import { LevelOptionType } from '../data/levelTier.mjs'; import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
import { damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs'; import { damageKeyToNumber } from '../helpers/utils.mjs';
export default class DhpActor extends Actor { export default class DhpActor extends Actor {
/** /**

View file

@ -1,11 +1,111 @@
@import '../../utils/colors.less'; @import '../../utils/colors.less';
.appTheme({}, {
&.daggerheart.dh-style.dialog.character-creation {
.setup-tabs button {
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
nav a .descriptor {
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
.main-selections-container {
.ancestry-mixed-controller label {
background-image: url(../assets/parchments/dh-parchment-dark.png);
}
.selections-container
.ancestry-preview-info-container
.ancestry-preview-features
.ancestry-preview-feature {
background-image: url(../assets/parchments/dh-parchment-light.png);
}
.traits-container {
.suggested-traits-container .suggested-trait-container {
background-image: url('../assets/parchments/dh-parchment-dark.png');
}
.traits-inner-container .trait-container {
background: url('../assets/svg/trait-shield-light.svg') no-repeat;
div {
filter: none;
text-shadow: none;
}
}
}
}
}
});
.daggerheart.dh-style.dialog.character-creation { .daggerheart.dh-style.dialog.character-creation {
.setup-tabs {
display: flex;
justify-content: center;
button {
background-image: url(../assets/parchments/dh-parchment-light.png);
border-radius: 6px;
border-color: light-dark(@dark-blue, @golden);
color: light-dark(@beige, @dark);
}
}
.main-selections-container { .main-selections-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
.ancestry-mixed-controller {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
gap: 4px;
margin-bottom: 8px;
&.active {
label {
opacity: 1;
}
}
label {
position: absolute;
font-size: 18px;
font-weight: bold;
padding: 0 2px;
background-image: url(../assets/parchments/dh-parchment-light.png);
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
color: light-dark(@beige, @dark);
opacity: 0.4;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
}
input {
width: 50%;
}
}
.ancestry-name {
display: flex;
justify-content: center;
width: 100%;
margin-bottom: 8px;
input {
width: 50%;
text-align: center;
}
}
.selections-container { .selections-container {
width: 140px; width: 140px;
display: flex; display: flex;
@ -15,6 +115,43 @@
.card-preview-container { .card-preview-container {
border-color: light-dark(@dark-blue, @golden); border-color: light-dark(@dark-blue, @golden);
} }
.ancestry-preview-info-container {
display: flex;
flex-direction: column;
gap: 4px;
width: 100%;
.ancestry-preview-label {
text-align: center;
font-weight: bold;
}
.ancestry-preview-features {
display: flex;
flex-direction: column;
justify-content: end;
gap: 2px;
width: 100%;
padding: 0 2px 2px 2px;
.ancestry-preview-feature {
flex: 1;
font-size: 14px;
white-space: wrap;
padding: 0 2px;
border: 1px solid light-dark(@golden, @dark-blue);
border-radius: 6px;
background-image: url(../assets/parchments/dh-parchment-dark.png);
color: light-dark(@dark, @beige);
height: min-content;
&.inactive {
opacity: 0.2;
}
}
}
}
} }
.selections-outer-container { .selections-outer-container {
@ -27,6 +164,10 @@
border-radius: 8px; border-radius: 8px;
border-color: light-dark(@dark-blue, @golden); border-color: light-dark(@dark-blue, @golden);
&.inactive {
opacity: 0.2;
}
legend { legend {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -44,6 +185,7 @@
legend { legend {
font-size: 20px; font-size: 20px;
white-space: nowrap;
} }
.action-button { .action-button {
@ -87,21 +229,38 @@
} }
.traits-inner-container { .traits-inner-container {
width: 100%;
display: flex; display: flex;
align-items: center;
justify-content: space-evenly; justify-content: space-evenly;
gap: 8px; gap: 8px;
.trait-container { .trait-container {
border: 1px solid light-dark(@dark-blue, @golden); width: 60px;
padding: 0 4px; height: 60px;
background: url(../assets/svg/trait-shield.svg) no-repeat;
div {
filter: drop-shadow(0 0 3px black);
text-shadow: 0 0 3px black;
}
select {
text-align: center;
width: 32px;
height: 24px;
position: relative;
top: 2px;
padding: 0;
}
} }
} }
} }
.experiences-inner-container { .experiences-inner-container {
display: flex; display: flex;
justify-content: space-evenly; flex-direction: column;
text-align: center; gap: 8px;
.experience-container { .experience-container {
position: relative; position: relative;

View file

@ -1,4 +1,8 @@
<section class="creation-action-footer"> <section class="creation-action-footer">
<button data-action="close">{{localize "Cancel"}}</button> <button data-action="close">{{localize "Cancel"}}</button>
<button {{#if tabs.setup.finished}}data-action="finish"{{else}}disabled{{/if}}>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.finishCreation"}}</button> {{#if isLastTab}}
<button data-action="finish" {{disabled (not tabs.setup.finished)}}>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.finishCreation"}}</button>
{{else}}
<button data-action="setupGoNext" {{disabled nextDisabled}}>{{localize "Next"}}</button>
{{/if}}
</section> </section>

View file

@ -0,0 +1,63 @@
<section
class='tab {{setupTabs.ancestry.cssClass}} {{setupTabs.ancestry.id}}'
data-tab='{{setupTabs.ancestry.id}}'
data-group='{{setupTabs.ancestry.group}}'
>
<div class="main-selections-container">
<fieldset class="section-container">
<legend>{{localize "TYPES.Item.ancestry"}}</legend>
<div class="ancestry-name">
<input type="text" name="ancestryName" value="{{ancestryName}}" placeholder="{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.ancestryNamePlaceholder"}}" {{disabled (not primaryAncestry.uuid)}} />
</div>
<div class="ancestry-mixed-controller {{#if mixedAncestry}}active{{/if}}">
<label class="mixed-ancestry-slider">
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.mixedAncestry"}}
<i class="fa-solid {{#if mixedAncestry}}fa-circle-check{{else}}fa-circle{{/if}}"></i>
</label>
<input type="range" id="volume" class="mixed-ancestry-slider" value="{{mixedAncestry}}" min="0" max="1" />
</div>
<div class="selections-outer-container">
<div class="selections-container primary-ancestry-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" primaryAncestry altPartialBlock=true secondaryDisabled=secondaryAncestry.uuid mixedAncestry=mixedAncestry }}
{{#if uuid}}
<div class="ancestry-preview-info-container">
<div class="ancestry-preview-label">{{name}}</div>
<div class="ancestry-preview-features">
<div class="ancestry-preview-feature" data-tooltip="{{concat "#item#" system.primaryFeature.uuid}}">{{system.primaryFeature.name}}</div>
<div class="ancestry-preview-feature {{#if secondaryDisabled}}inactive{{/if}}" data-tooltip="{{concat "#item#" system.secondaryFeature.uuid}}">{{system.secondaryFeature.name}}</div>
</div>
</div>
{{else}}
{{#if mixedAncestry}}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectPrimaryAncestry"}}
{{else}}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectAncestry"}}
{{/if}}
{{/if}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
{{#if mixedAncestry}}
<div class="selections-container secondary-ancestry-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" secondaryAncestry altPartialBlock=true }}
{{#if uuid}}
<div class="ancestry-preview-info-container">
<div class="ancestry-preview-label">{{name}}</div>
<div class="ancestry-preview-features">
<div class="ancestry-preview-feature inactive" data-tooltip="{{concat "#item#" system.primaryFeature.uuid}}">{{system.primaryFeature.name}}</div>
<div class="ancestry-preview-feature" data-tooltip="{{concat "#item#" system.secondaryFeature.uuid}}">{{system.secondaryFeature.name}}</div>
</div>
</div>
{{else}}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectSecondaryAncestry"}}
{{/if}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
{{/if}}
</div>
</fieldset>
</div>
</section>

View file

@ -0,0 +1,24 @@
<section
class='tab {{setupTabs.class.cssClass}} {{setupTabs.class.id}}'
data-tab='{{setupTabs.class.id}}'
data-group='{{setupTabs.class.group}}'
>
<div class="main-selections-container">
<fieldset class="section-container">
<legend>{{localize "TYPES.Item.class"}}</legend>
<div class="selections-outer-container">
<div class="selections-container class-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" class }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectClass"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
<div class="selections-container subclass-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" subclass disabled=(not class.img) }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectSubclass"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
</div>
</fieldset>
</div>
</section>

View file

@ -0,0 +1,18 @@
<section
class='tab {{setupTabs.community.cssClass}} {{setupTabs.community.id}}'
data-tab='{{setupTabs.community.id}}'
data-group='{{setupTabs.community.group}}'
>
<div class="main-selections-container">
<fieldset class="section-container">
<legend>{{localize "TYPES.Item.community"}}</legend>
<div class="selections-outer-container">
<div class="selections-container community-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" community }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectCommunity"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
</div>
</fieldset>
</div>
</section>

View file

@ -0,0 +1,22 @@
<section
class='tab {{setupTabs.domainCards.cssClass}} {{setupTabs.domainCards.id}}'
data-tab='{{setupTabs.domainCards.id}}'
data-group='{{setupTabs.domainCards.group}}'
>
<div class="main-selections-container">
<fieldset class="section-container">
<legend>{{localize "TYPES.Item.domainCard"}}</legend>
<div class="selections-outer-container">
{{#each domainCards as |domainCard id|}}
<div class="selections-container domain-card" data-card="{{id}}">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" domainCard }}
{{#each @root.class.system.domains }}
<div>{{localize (concat "DAGGERHEART.GENERAL.Domain." this ".label")}}</div>
{{/each}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
{{/each}}
</div>
</fieldset>
</div>
</section>

View file

@ -0,0 +1,19 @@
<section
class='tab {{setupTabs.experience.cssClass}} {{setupTabs.experience.id}}'
data-tab='{{setupTabs.experience.id}}'
data-group='{{setupTabs.experience.group}}'
>
<div class="main-selections-container">
<fieldset class="section-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.initialExperiences"}} {{experience.nrSelected}}/{{experience.nrTotal}}</legend>
<div class="experiences-inner-container">
{{#each experience.values as |experience id|}}
<div class="experience-container">
<input class="experience-description" type="text" name="{{concat "experiences." id ".name" }}" value="{{experience.name}}" placeholder="{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.newExperience"}}" />
<div class="experience-value">{{numberFormat this.value sign=true}}</div>
</div>
{{/each}}
</div>
</fieldset>
</div>
</section>

View file

@ -0,0 +1,32 @@
<section
class='tab {{setupTabs.traits.cssClass}} {{setupTabs.traits.id}}'
data-tab='{{setupTabs.traits.id}}'
data-group='{{setupTabs.traits.group}}'
>
<div class="main-selections-container">
<fieldset class="section-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.traitIncreases"}} {{traits.nrSelected}}/{{traits.nrTotal}}</legend>
<div class="traits-container">
<fieldset class="section-inner-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.suggestedTraits"}}</legend>
<div class="suggested-traits-container">
{{#each suggestedTraits}}
<div class="suggested-trait-container">{{this}}</div>
{{/each}}
</div>
<button class="action-button" data-action="useSuggestedTraits">{{localize "Use"}}</button>
</fieldset>
<div class="traits-inner-container">
{{#each traits.values}}
<div class="trait-container">
<div>{{this.name}}</div>
<select name="{{concat "traits." this.key ".value"}}" data-dtype="Number">
{{selectOptions this.options selected=this.value valueAttr="key" labelAttr="value" blank=""}}
</select>
</div>
{{/each}}
</div>
</div>
</fieldset>
</div>
</section>

View file

@ -3,99 +3,11 @@
data-tab='{{tabs.setup.id}}' data-tab='{{tabs.setup.id}}'
data-group='{{tabs.setup.group}}' data-group='{{tabs.setup.group}}'
> >
<div class="main-selections-container"> <nav class='feature-tab sheet-tabs tabs setup-tabs' data-group='setup'>
<fieldset class="section-container"> {{#each setupTabs as |tab|}}
<legend>{{localize "TYPES.Item.class"}}</legend> <button class='{{tab.id}} {{tab.cssClass}}' data-action='tab' data-group='{{tab.group}}' data-tab='{{tab.id}}' {{disabled tab.disabled}}>
<div class="selections-outer-container"> {{localize tab.label}}
<div class="selections-container class-card"> </button>
{{#> "systems/daggerheart/templates/components/card-preview.hbs" class }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectClass"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
<div class="selections-container subclass-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" subclass disabled=(not class.img) }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectSubclass"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
</div>
</fieldset>
{{#if (gte visibility 2)}}
<fieldset class="section-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.heritage"}}</legend>
<div class="selections-outer-container">
<div class="selections-container ancestry-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" ancestry }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectAncestry"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
<div class="selections-container community-card">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" community }}
{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.selectCommunity"}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
</div>
</fieldset>
{{/if}}
{{#if (gte visibility 3)}}
<fieldset class="section-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.traitIncreases"}} {{traits.nrSelected}}/{{traits.nrTotal}}</legend>
<div class="traits-container">
<fieldset class="section-inner-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.suggestedTraits"}}</legend>
<div class="suggested-traits-container">
{{#each suggestedTraits}}
<div class="suggested-trait-container">{{this}}</div>
{{/each}} {{/each}}
</div> </nav>
<button class="action-button" data-action="useSuggestedTraits">{{localize "Use"}}</button>
</fieldset>
<div class="traits-inner-container">
{{#each traits.values}}
<div class="trait-container">
<div>{{this.name}}</div>
<select name="{{concat "traits." this.key ".value"}}" data-dtype="Number">
{{selectOptions this.options selected=this.value valueAttr="key" labelAttr="value" blank=""}}
</select>
</div>
{{/each}}
</div>
</div>
</fieldset>
{{/if}}
{{#if (gte visibility 4)}}
<fieldset class="section-container">
<legend>{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.initialExperiences"}} {{experience.nrSelected}}/{{experience.nrTotal}}</legend>
<div class="experiences-inner-container">
{{#each experience.values as |experience id|}}
<div class="experience-container">
<input class="experience-description" type="text" name="{{concat "experiences." id ".description" }}" value="{{experience.description}}" placeholder="{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.newExperience"}}" />
<div class="experience-value">{{numberFormat this.value sign=true}}</div>
</div>
{{/each}}
</div>
</fieldset>
{{/if}}
{{#if (gte visibility 5)}}
<fieldset class="section-container">
<legend>{{localize "TYPES.Item.domainCard"}}</legend>
<div class="selections-outer-container">
{{#each domainCards as |domainCard id|}}
<div class="selections-container domain-card" data-card="{{id}}">
{{#> "systems/daggerheart/templates/components/card-preview.hbs" domainCard }}
{{#each @root.class.system.domains }}
<div>{{localize (concat "DAGGERHEART.GENERAL.Domain." this ".label")}}</div>
{{/each}}
{{/"systems/daggerheart/templates/components/card-preview.hbs"}}
</div>
{{/each}}
</div>
</fieldset>
{{/if}}
</div>
</section> </section>

View file

@ -4,7 +4,13 @@
> >
{{#if this.img}} {{#if this.img}}
<img class="preview-image-container" src="{{this.img}}" /> <img class="preview-image-container" src="{{this.img}}" />
<div class="preview-text-container">{{this.name}}</div> <div class="preview-text-container">
{{#if altPartialBlock}}
{{> @partial-block }}
{{else}}
{{this.name}}
{{/if}}
</div>
{{else}} {{else}}
<div class="preview-empty-container"> <div class="preview-empty-container">
<div class="preview-empty-inner-container"> <div class="preview-empty-inner-container">

View file

@ -15,7 +15,7 @@
{{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}} {{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}}
<button <button
type="button" type="button"
class="level-button glow" data-tooltip="{{#if document.system.needsCharacterSetup}}{{localize "DAGGERHEART.Sheets.PC.CharacterSetup"}}{{else}}{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}{{/if}}" class="level-button glow" data-tooltip="{{#if document.system.needsCharacterSetup}}{{localize "DAGGERHEART.APPLICATIONS.CharacterCreation.buttonTitle"}}{{else}}{{localize "DAGGERHEART.ACTORS.Character.levelUp"}}{{/if}}"
data-action="levelManagement" data-action="levelManagement"
> >
<i class="fa-solid fa-triangle-exclamation"></i> <i class="fa-solid fa-triangle-exclamation"></i>

View file

@ -0,0 +1,47 @@
<section
class='tab {{tabs.features.cssClass}} {{tabs.features.id}}'
data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}'
>
<fieldset class="one-column drop-section primary-feature">
<legend>
{{localize "DAGGERHEART.ITEMS.Ancestry.primaryFeature"}}
{{#unless document.system.primaryFeature}}<a><i data-action="addFeature" data-type="primary" class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
</legend>
<div class="features-list">
{{#if document.system.primaryFeature}}
<div class="feature-item"
data-action="editFeature"
data-type="primary"
>
<img class="image" src="{{document.system.primaryFeature.img}}" />
<span>{{document.system.primaryFeature.name}}</span>
<div class="controls">
<a data-action="removeFeature" data-type="primary"><i class="fa-solid fa-trash"></i></a>
</div>
</div>
{{/if}}
</div>
</fieldset>
<fieldset class="one-column drop-section secondary-feature">
<legend>
{{localize "DAGGERHEART.ITEMS.Ancestry.secondaryFeature"}}
{{#unless document.system.secondaryFeature}}<a><i data-action="addFeature" data-type="secondary" class="fa-solid fa-plus icon-button"></i></a>{{/unless}}
</legend>
<div class="features-list">
{{#if document.system.secondaryFeature}}
<div class="feature-item"
data-action="editFeature"
data-type="secondary"
>
<img class="image" src="{{document.system.secondaryFeature.img}}" />
<span>{{document.system.secondaryFeature.name}}</span>
<div class="controls">
<a data-action="removeFeature" data-type="secondary"><i class="fa-solid fa-trash"></i></a>
</div>
</div>
{{/if}}
</div>
</fieldset>
</section>