Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications

This commit is contained in:
Joaquin Pereyra 2025-07-14 15:49:20 -03:00
commit d26ed22e74
69 changed files with 1781 additions and 446 deletions

View file

@ -153,12 +153,25 @@
},
"APPLICATIONS": {
"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",
"finishCreation": "Finish Character Setup",
"heritage": "Heritage",
"initialExperiences": "Initial Experiences",
"mixedAncestry": "Mixed Ancestry",
"newExperience": "New Experience..",
"selectAncestry": "Select Ancestry",
"selectPrimaryAncestry": "Select Primary Ancestry",
"selectSecondaryAncestry": "Select Secondary Ancestry",
"selectArmor": "Select Armor",
"selectClass": "Select Class",
"selectCommunity": "Select Community",
@ -388,6 +401,10 @@
"OwnershipSelection": {
"title": "Ownership Selection - {name}",
"default": "Default Ownership"
},
"ResourceDice": {
"title": "{name} Resource",
"rerollDice": "Reroll Dice"
}
},
@ -633,6 +650,10 @@
"abbreviation": "AS"
}
},
"ItemResourceType": {
"simple": "Simple",
"diceValue": "Dice Value"
},
"Range": {
"self": {
"name": "Self",
@ -1069,6 +1090,10 @@
"shortrest": "Short Rest",
"longrest": "Long Rest"
},
"Resource": {
"single": "Resource",
"plural": "Resources"
},
"Tabs": {
"details": "Details",
"attack": "Attack",
@ -1155,6 +1180,21 @@
"value": "Value"
},
"ITEMS": {
"FIELDS": {
"resource": {
"amount": { "label": "Amount" },
"dieFaces": { "label": "Die Faces" },
"icon": { "label": "Icon" },
"max": { "label": "Max" },
"recovery": { "label": "Recovery" },
"type": { "label": "Type" },
"value": { "label": "Value" }
}
},
"Ancestry": {
"primaryFeature": "Primary Feature",
"secondaryFeature": "Secondary Feature"
},
"Armor": {
"baseScore": "Base Score",
"baseThresholds": {
@ -1332,8 +1372,8 @@
},
"UI": {
"Chat": {
"dualityRoll": {
"abilityCheckTitle": "{ability} Check"
"applyEffect": {
"title": "Apply Effects - {name}"
},
"attackRoll": {
"title": "Attack - {attack}",
@ -1349,25 +1389,28 @@
"hitTarget": "Hit Targets",
"selectedTarget": "Selected"
},
"applyEffect": {
"title": "Apply Effects - {name}"
},
"healingRoll": {
"title": "Heal - {healing}",
"heal": "Heal"
},
"deathMove": {
"title": "Death Move"
},
"domainCard": {
"title": "Domain Card"
},
"dualityRoll": {
"abilityCheckTitle": "{ability} Check"
},
"featureTitle": "Class Feature",
"foundationCard": {
"ancestryTitle": "Ancestry Card",
"communityTitle": "Community Card",
"subclassFeatureTitle": "Subclass Feature"
},
"featureTitle": "Class Feature"
"healingRoll": {
"title": "Heal - {healing}",
"heal": "Heal"
},
"resourceRoll": {
"playerMessage": "{user} rerolled their {name}"
}
},
"Notifications": {
"adversaryMissing": "The linked adversary doesn't exist in the world.",
@ -1407,7 +1450,8 @@
"damageAlreadyNone": "The damage has already been reduced to none",
"noAvailableArmorMarks": "You have no more available armor marks",
"notEnoughStress": "You don't have enough stress",
"damageIgnore": "{character} did not take damage"
"damageIgnore": "{character} did not take damage",
"featureIsMissing": "Feature is missing"
},
"Tooltip": {
"disableEffect": "Disable Effect",

View file

@ -11,13 +11,16 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.setup = {
traits: this.character.system.traits,
ancestry: this.character.system.ancestry ?? {},
ancestryName: '',
mixedAncestry: false,
primaryAncestry: this.character.system.ancestry ?? {},
secondaryAncestry: {},
community: this.character.system.community ?? {},
class: this.character.system.class?.value ?? {},
subclass: this.character.system.class?.subclass ?? {},
experiences: {
[foundry.utils.randomID()]: { description: '', value: 2 },
[foundry.utils.randomID()]: { description: '', value: 2 }
[foundry.utils.randomID()]: { name: '', value: 2 },
[foundry.utils.randomID()]: { name: '', value: 2 }
},
domainCards: {
[foundry.utils.randomID()]: {},
@ -47,12 +50,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'character-creation'],
position: { width: 800, height: 'auto' },
position: { width: 700, height: 'auto' },
actions: {
viewCompendium: this.viewCompendium,
viewItem: this.viewItem,
useSuggestedTraits: this.useSuggestedTraits,
equipmentChoice: this.equipmentChoice,
setupGoNext: this.setupGoNext,
finish: this.finish
},
form: {
@ -76,6 +80,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/characterCreation/tabs.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' },
// story: { template: 'systems/daggerheart/templates/characterCreation/tabs/story.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) {
for (const v of Object.values(tabs)) {
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) {
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 heritageFinished = this.setup.ancestry.uuid && this.setup.community.uuid;
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);
v.finished =
ancestryFinished &&
communityFinished &&
classFinished &&
heritageFinished &&
traitsFinished &&
experiencesFinished &&
domainCardsFinished;
@ -146,15 +203,44 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
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) {
super.changeTab(tab, group, options);
for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) {
marker.classList.add('active');
} else {
marker.classList.remove('active');
if (group === 'primary') {
for (var listTab of Object.keys(this.constructor.TABS)) {
const marker = options.navElement.querySelector(`a[data-action="tab"].${listTab} .finish-marker`);
if (listTab === tab) {
marker.classList.add('active');
} else {
marker.classList.remove('active');
}
}
}
}
@ -163,6 +249,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
super._attachPartListeners(partId, htmlElement, options);
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) {
@ -174,7 +265,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _preparePartContext(partId, context) {
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':
context.setupTabs = this._getSetupTabs(this.constructor.SETUPTABS);
const availableTraitModifiers = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew)
.traitArray.map(trait => ({ key: trait, name: trait }));
@ -215,13 +329,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
context.experience = {
values: this.setup.experiences,
nrTotal: Object.keys(this.setup.experiences).length,
nrSelected: Object.values(this.setup.experiences).reduce(
(acc, exp) => acc + (exp.description ? 1 : 0),
0
)
nrSelected: Object.values(this.setup.experiences).reduce((acc, exp) => acc + (exp.name ? 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.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
@ -278,18 +392,29 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
this.render();
}
mixedAncestryToggle(event) {
event.preventDefault();
event.stopPropagation();
this.setup.mixedAncestry = !this.setup.mixedAncestry;
if (!this.setup.mixedAncestry) this.setup.secondaryAncestry = {};
this.render();
}
getUpdateVisibility() {
switch (this.setup.visibility) {
case 6:
return 6;
case 5:
return 5;
return Object.values(this.setup.experiences).every(x => x.name) ? 6 : 5;
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:
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:
return this.setup.ancestry.uuid && this.setup.community.uuid ? 3 : 2;
return this.setup.community.uuid ? 3 : 2;
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();
}
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() {
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.class]);
await this.character.createEmbeddedDocuments('Item', [this.setup.subclass]);
@ -396,8 +559,15 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
if (item.type === 'ancestry' && event.target.closest('.ancestry-card')) {
this.setup.ancestry = {
if (item.type === 'ancestry' && event.target.closest('.primary-ancestry-card')) {
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,
effects: Array.from(item.effects).map(x => x.toObject()),
uuid: item.uuid

View file

@ -7,3 +7,4 @@ export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs';
export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';

View file

@ -66,7 +66,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.canRoll = true;
if (this.config.costs?.length) {
const updatedCosts = this.action.calcCosts(this.config.costs);
context.costs = updatedCosts;
context.costs = updatedCosts.map(x => ({
...x,
label: x.keyIsID
? this.action.parent.parent.name
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
}));
context.canRoll = this.action.hasCost(updatedCosts);
this.config.data.scale = this.config.costs[0].total;
}
@ -74,7 +79,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.uses = this.action.calcUses(this.config.uses);
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
}
if(this.roll) {
if (this.roll) {
context.roll = this.roll;
context.rollType = this.roll?.constructor.name;
context.experiences = Object.keys(this.config.data.experiences).map(id => ({

View file

@ -0,0 +1,99 @@
import { itemAbleRollParse } from '../../helpers/utils.mjs';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class ResourceDiceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(item, actor, options = {}) {
super(options);
this.item = item;
this.actor = actor;
this.diceStates = foundry.utils.deepClone(item.system.resource.diceStates);
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'resource-dice'],
window: {
icon: 'fa-solid fa-dice'
},
actions: {
rerollDice: this.rerollDice,
save: this.save
},
form: {
handler: this.updateResourceDice,
submitOnChange: true,
submitOnClose: false
}
};
/** @override */
static PARTS = {
resourceDice: {
id: 'resourceDice',
template: 'systems/daggerheart/templates/dialogs/dice-roll/resourceDice.hbs'
}
};
get title() {
return game.i18n.format('DAGGERHEART.APPLICATIONS.ResourceDice.title', { name: this.item.name });
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.item = this.item;
context.actor = this.actor;
context.diceStates = this.diceStates;
return context;
}
static async updateResourceDice(event, _, formData) {
const { diceStates } = foundry.utils.expandObject(formData.object);
this.diceStates = Object.keys(diceStates).reduce((acc, key) => {
const resourceState = this.item.system.resource.diceStates[key];
acc[key] = { ...diceStates[key], used: Boolean(resourceState?.used) };
return acc;
}, {});
this.render();
}
static async save() {
this.rollValues = Object.values(this.diceStates);
this.close();
}
static async rerollDice() {
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
const diceFormula = `${max}d${this.item.system.resource.dieFaces}`;
const roll = await new Roll(diceFormula).evaluate();
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
this.resetUsed = true;
const cls = getDocumentClass('ChatMessage');
const msg = new cls({
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/resource-roll.hbs',
{
user: this.actor.name,
name: this.item.name
}
)
});
cls.create(msg.toObject());
this.close();
}
static async create(item, actor, options = {}) {
return new Promise(resolve => {
const app = new this(item, actor, options);
app.addEventListener('close', () => resolve(app.rollValues), { once: true });
app.render({ force: true });
});
}
}

View file

@ -107,6 +107,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
context.hasBaseDamage = !!this.action.parent.attack;
context.getRealIndex = this.getRealIndex.bind(this);
context.getEffectDetails = this.getEffectDetails.bind(this);
context.costOptions = this.getCostOptions();
context.disableOption = this.disableOption.bind(this);
context.isNPC = this.action.actor && this.action.actor.type !== 'character';
context.hasRoll = this.action.hasRoll;
@ -125,8 +126,21 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
this.render(true);
}
disableOption(index, options, choices) {
const filtered = foundry.utils.deepClone(options);
getCostOptions() {
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
const resource = this.action.parent.resource;
if (resource) {
options[this.action.parent.parent.id] = {
label: this.action.parent.parent.name,
group: 'TYPES.Actor.character'
};
}
return options;
}
disableOption(index, costOptions, choices) {
const filtered = foundry.utils.deepClone(costOptions);
Object.keys(filtered).forEach(o => {
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
});
@ -142,11 +156,19 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
return this.action.item.effects.get(id);
}
_prepareSubmitData(event, formData) {
_prepareSubmitData(_event, formData) {
const submitData = foundry.utils.expandObject(formData.object);
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
const data = foundry.utils.getProperty(submitData, keyPath);
if (data) foundry.utils.setProperty(submitData, keyPath, Object.values(data));
const dataValues = data ? Object.values(data) : [];
if (keyPath === 'cost') {
for (var value of dataValues) {
const item = this.action.parent.parent.id === value.key;
value.keyIsID = Boolean(item);
}
}
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
}
return submitData;
}

View file

@ -4,7 +4,7 @@ import { abilities } from '../../../config/actorConfig.mjs';
import DhCharacterlevelUp from '../../levelup/characterLevelup.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
import FilterMenu from '../../ux/filter-menu.mjs';
import { getDocFromElement } from "../../../helpers/utils.mjs";
import { getDocFromElement, itemAbleRollParse } from "../../../helpers/utils.mjs";
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -23,6 +23,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
makeDeathMove: CharacterSheet.#makeDeathMove,
levelManagement: CharacterSheet.#levelManagement,
toggleEquipItem: CharacterSheet.#toggleEquipItem,
useItem: this.useItem, //TODO Fix this
useAction: this.useAction,
toggleResourceDice: this.toggleResourceDice,
handleResourceDice: this.handleResourceDice,
toChat: this.toChat
},
window: {
resizable: true
@ -102,6 +107,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement.querySelectorAll('.inventory-item-resource').forEach(element => {
element.addEventListener('change', this.updateItemResource.bind(this));
});
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
element.addEventListener('change', this.updateItemQuantity.bind(this));
});
}
/** @inheritDoc */
async _onRender(context, options) {
await super._onRender(context, options);
@ -486,6 +502,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
}
/* -------------------------------------------- */
/* Application Listener Actions */
/* -------------------------------------------- */
async updateItemResource(event) {
const item = this.getItem(event.currentTarget);
if (!item) return;
const max = item.system.resource.max ? itemAbleRollParse(item.system.resource.max, this.document, item) : null;
const value = max ? Math.min(Number(event.currentTarget.value), max) : event.currentTarget.value;
await item.update({ 'system.resource.value': value });
this.render();
}
async updateItemQuantity(event) {
const item = this.getItem(event.currentTarget);
if (!item) return;
await item.update({ 'system.quantity': event.currentTarget.value });
this.render();
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
@ -639,16 +676,81 @@ export default class CharacterSheet extends DHBaseActorSheet {
action.use(event);
}
/**
* Toggle the used state of a resource dice.
* @type {ApplicationClickAction}
*/
static async toggleResourceDice(event) {
const target = event.target.closest('.item-resource');
const item = this.getItem(event);
if (!item) return;
const diceState = item.system.resource.diceStates[target.dataset.dice];
await item.update({
[`system.resource.diceStates.${target.dataset.dice}.used`]: diceState?.used ? !diceState.used : true
});
}
/**
* Handle the roll values of resource dice.
* @type {ApplicationClickAction}
*/
static async handleResourceDice(event) {
const item = this.getItem(event);
if (!item) return;
const rollValues = await game.system.api.applications.dialogs.ResourceDiceDialog.create(item, this.document);
if (!rollValues) return;
await item.update({
'system.resource.diceStates': rollValues.reduce((acc, state, index) => {
acc[index] = { value: state.value, used: state.used };
return acc;
}, {})
});
this.render();
}
/**
* Send item to Chat
* @type {ApplicationClickAction}
*/
static async toChat(event, button) {
if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage');
const systemData = {
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
description: `${experience.name} ${experience.value.signedString()}`
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
} else {
const item = this.getItem(event);
if (!item) return;
item.toChat(this.document.id);
}
}
async _onDragStart(event) {
const item = this.getItem(event);
const dragData = {
type: item.documentName,
uuid: item.uuid
};
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
super._onDragStart(event);
}
@ -656,7 +758,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
// Prevent event bubbling to avoid duplicate handling
event.preventDefault();
event.stopPropagation();
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));
}

View file

@ -21,6 +21,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
},
actions: {
removeAction: DHBaseItemSheet.#removeAction,
addFeature: DHBaseItemSheet.#addFeature,
editFeature: DHBaseItemSheet.#editFeature,
removeFeature: DHBaseItemSheet.#removeFeature,
addResource: DHBaseItemSheet.#addResource,
removeResource: DHBaseItemSheet.#removeResource
addFeature: DHBaseItemSheet.#addFeature
},
dragDrop: [
@ -179,6 +184,71 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
'system.features': [...this.document.system.features, feature]
});
}
/**
* 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.features.find(x => x?.id === target.id);
if (!feature) {
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.features.find(x => x && x.id === target.id);
if (feature) {
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;
}
await this.document.update({
'system.features': this.document.system.features
.filter(feature => feature && feature.id !== target.id)
.map(x => x.uuid)
});
}
/**
* Add a resource to the item.
* @type {ApplicationClickAction}
*/
static async #addResource() {
await this.document.update({
'system.resource': { type: 'simple', value: 0 }
});
}
/**
* Remove the resource from the item.
* @type {ApplicationClickAction}
*/
static async #removeResource() {
await this.document.update({
'system.resource': null
});
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
@ -193,7 +263,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (featureItem) {
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsMissing'));
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}

View file

@ -10,10 +10,6 @@ export default class DHHeritageSheet extends DHBaseItemSheet {
static PARTS = {
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']

View file

@ -1,4 +1,3 @@
export default function ItemAttachmentSheet(Base) {
return class extends Base {
static DEFAULT_OPTIONS = {
@ -25,10 +24,7 @@ export default function ItemAttachmentSheet(Base) {
...super.TABS,
primary: {
...super.TABS?.primary,
tabs: [
...(super.TABS?.primary?.tabs || []),
{ id: 'attachments' }
],
tabs: [...(super.TABS?.primary?.tabs || []), { id: 'attachments' }],
initial: super.TABS?.primary?.initial || 'description',
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
}
@ -46,29 +42,28 @@ export default function ItemAttachmentSheet(Base) {
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const attachmentsSection = event.target.closest('.attachments-section');
if (!attachmentsSection) return super._onDrop(event);
event.preventDefault();
event.stopPropagation();
const item = await Item.implementation.fromDropData(data);
if (!item) return;
// Call the data model's public method
await this.document.system.addAttachment(item);
}
static async #removeAttachment(event, target) {
// Call the data model's public method
await this.document.system.removeAttachment(target.dataset.uuid);
}
}
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
// Keep this simple UI preparation in the mixin
const attachedUUIDs = this.document.system.attached;
@ -83,8 +78,8 @@ export default function ItemAttachmentSheet(Base) {
})
);
}
return context;
}
};
}
}

View file

@ -3,12 +3,109 @@ import DHHeritageSheet from '../api/heritage-sheet.mjs';
export default class AncestrySheet extends DHHeritageSheet {
/**@inheritdoc */
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 */
static PARTS = {
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

@ -87,6 +87,16 @@ export default class ClassSheet extends DHBaseItemSheet {
await this.document.update({
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
});
} else if (item.type === 'feature') {
if (target.classList.contains('hope-feature')) {
await this.document.update({
'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid]
});
} else if (target.classList.contains('class-feature')) {
await this.document.update({
'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid]
});
}
} else if (item.type === 'weapon') {
if (target.classList.contains('primary-weapon-section')) {
if (!this.document.system.characterGuide.suggestedPrimaryWeapon && !item.system.secondary)
@ -146,7 +156,7 @@ export default class ClassSheet extends DHBaseItemSheet {
static async #removeItemFromCollection(_event, element) {
const { uuid, target } = element.dataset;
const prop = foundry.utils.getProperty(this.document.system, target);
await this.document.update({ [target]: prop.filter(i => i.uuid !== uuid) });
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid) });
}
/**

View file

@ -9,6 +9,10 @@ export default class CommunitySheet extends DHHeritageSheet {
/**@inheritdoc */
static PARTS = {
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

@ -13,6 +13,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
header: { template: 'systems/daggerheart/templates/sheets/items/feature/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
description: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-description.hbs' },
settings: { template: 'systems/daggerheart/templates/sheets/items/feature/settings.hbs' },
actions: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-actions.hbs',
scrollable: ['.actions']
@ -26,7 +27,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
/**@override */
static TABS = {
primary: {
tabs: [{ id: 'description' }, { id: 'actions' }, { id: 'effects' }],
tabs: [{ id: 'description' }, { id: 'settings' }, { id: 'actions' }, { id: 'effects' }],
initial: 'description',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}

View file

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

View file

@ -1,6 +1,6 @@
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor() {
super();
constructor(options) {
super(options);
this.targetTemplate = {
activeLayer: undefined,
@ -14,6 +14,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
addChatListeners = async (app, html, data) => {
super.addChatListeners(app, html, data);
html.querySelectorAll('.duality-action-damage').forEach(element =>
element.addEventListener('click', event => this.onRollDamage(event, data.message))
);

View file

@ -8,4 +8,4 @@ export const encounterCountdown = {
position: 'countdown-encounter-position'
};
export const itemAttachmentSource = 'attachmentSource';
export const itemAttachmentSource = 'attachmentSource';

View file

@ -366,31 +366,6 @@ export const abilityCosts = {
label: 'Armor Stack',
group: 'TYPES.Actor.character'
},
prayer: {
id: 'prayer',
label: 'Prayer Dice',
group: 'TYPES.Actor.character'
},
favor: {
id: 'favor',
label: 'Favor Points',
group: 'TYPES.Actor.character'
},
slayer: {
id: 'slayer',
label: 'Slayer Dice',
group: 'TYPES.Actor.character'
},
tide: {
id: 'tide',
label: 'Tide',
group: 'TYPES.Actor.character'
},
chaos: {
id: 'chaos',
label: 'Chaos',
group: 'TYPES.Actor.character'
},
fear: {
id: 'fear',
label: 'Fear',

View file

@ -1320,6 +1320,11 @@ export const featureTypes = {
}
};
export const featureSubTypes = {
primary: 'primary',
secondary: 'secondary'
};
export const actionTypes = {
passive: {
id: 'passive',
@ -1334,3 +1339,14 @@ export const actionTypes = {
label: 'DAGGERHEART.CONFIG.ActionType.reaction'
}
};
export const itemResourceTypes = {
simple: {
id: 'simple',
label: 'DAGGERHEART.CONFIG.ItemResourceType.simple'
},
diceValue: {
id: 'diceValue',
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
}
};

View file

@ -107,7 +107,7 @@ export class DHDamageData extends foundry.abstract.DataModel {
}),
{
label: 'Type',
initial: 'physical',
initial: 'physical'
}
),
resultBased: new fields.BooleanField({

View file

@ -1,4 +1,4 @@
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField } from './actionDice.mjs';
import { DHActionDiceData, DHActionRollData, DHDamageField } from './actionDice.mjs';
import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
@ -35,12 +35,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
}),
cost: new fields.ArrayField(
new fields.SchemaField({
type: new fields.StringField({
choices: CONFIG.DH.GENERAL.abilityCosts,
key: new fields.StringField({
nullable: false,
required: true,
initial: 'hope'
}),
keyIsID: new fields.BooleanField(),
value: new fields.NumberField({ nullable: true, initial: 1 }),
scalable: new fields.BooleanField({ initial: false }),
step: new fields.NumberField({ nullable: true, initial: null })
@ -181,7 +181,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
// Add Roll results to RollDatas
actorData.result = data.roll?.total ?? 1;
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
? (data.costs.find(c => c.scalable)?.total ?? 1)
: 1;
@ -204,7 +204,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
// Prepare Costs
const costsConfig = this.prepareCost();
if (isFastForward && !this.hasCost(costsConfig))
if (isFastForward && !(await this.hasCost(costsConfig)))
return ui.notifications.warn("You don't have the resources to use that action.");
// Prepare Uses
@ -278,7 +278,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
prepareCost() {
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
return costs;
return this.calcCosts(costs);
}
prepareUse() {
@ -327,11 +327,26 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
}
async consume(config) {
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
for (var cost of config.costs) {
if (cost.keyIsID) {
usefulResources[cost.key] = {
value: cost.value,
target: this.parent.parent,
keyIsID: true
};
}
}
const resources = config.costs
.filter(c => c.enabled !== false)
.map(c => {
const resource = this.actor.system.resources[c.type];
return { type: c.type, value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1) };
const resource = usefulResources[c.key];
return {
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
};
});
await this.actor.modifyResource(resources);
@ -372,9 +387,27 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
});
}
hasCost(costs) {
async getResources(costs) {
const actorResources = this.actor.system.resources;
const itemResources = {};
for (var itemResource of costs) {
if (itemResource.keyIsID) {
itemResources[itemResource.key] = {
value: this.parent.resource.value ?? 0
};
}
}
return {
...actorResources,
...itemResources
};
}
/* COST */
async hasCost(costs) {
const realCosts = this.getRealCosts(costs),
hasFearCost = realCosts.findIndex(c => c.type === 'fear');
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
if (hasFearCost > -1) {
const fearCost = realCosts.splice(hasFearCost, 1)[0];
if (
@ -385,16 +418,15 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
}
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
const resources = this.actor.system.resources;
const resources = await this.getResources(realCosts);
return realCosts.reduce(
(a, c) =>
a && resources[c.type].isReversed
? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].max
: resources[c.type]?.value >= (c.total ?? c.value),
a && resources[c.key].isReversed
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
: resources[c.key]?.value >= (c.total ?? c.value),
true
);
}
/* COST */
/* USES */
calcUses(uses) {
@ -409,7 +441,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
if (!uses) return true;
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
}
/* USES */
/* TARGET */
isTargetFriendly(target) {

View file

@ -11,8 +11,8 @@ export default class DHDamageAction extends DHBaseAction {
async rollDamage(event, data) {
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
damageTypes = [...new Set(this.damage.parts.reduce((a,c) => a.concat([...c.type]), []))];
damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))];
damageTypes = !damageTypes.length ? ['physical'] : damageTypes;
if (!formula || formula == '') return;
@ -36,7 +36,7 @@ export default class DHDamageAction extends DHBaseAction {
config.source.message = data._id;
config.directDamage = false;
}
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);
}
}

View file

@ -1,4 +1,4 @@
import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs";
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
const resistanceField = () =>
new foundry.data.fields.SchemaField({
@ -37,13 +37,12 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
const fields = foundry.data.fields;
const schema = {};
if(this.metadata.isNPC)
schema.description = new fields.HTMLField({ required: true, nullable: true });
if(this.metadata.hasResistances)
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
if (this.metadata.hasResistances)
schema.resistance = new fields.SchemaField({
physical: resistanceField(),
magical: resistanceField()
})
});
return schema;
}

View file

@ -39,9 +39,7 @@ export default class DhCharacter extends BaseDataActor {
resources: new fields.SchemaField({
hitPoints: resourceField(0, true),
stress: resourceField(6, true),
hope: resourceField(6),
tokens: new fields.ObjectField(),
dice: new fields.ObjectField()
hope: resourceField(6)
}),
traits: new fields.SchemaField({
agility: attributeField(),
@ -292,9 +290,7 @@ export default class DhCharacter extends BaseDataActor {
}
get deathMoveViable() {
return (
this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max
);
return this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max;
}
get armorApplicableDamageTypes() {

View file

@ -18,4 +18,20 @@ export default class DHAncestry extends BaseDataItem {
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

@ -10,7 +10,6 @@ export default class DHArmor extends AttachableItem {
label: 'TYPES.Item.armor',
type: 'armor',
hasDescription: true,
isQuantifiable: true,
isInventoryItem: true
});
}

View file

@ -5,7 +5,7 @@ export default class AttachableItem extends BaseDataItem {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: "Item", nullable: true }))
attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: 'Item', nullable: true }))
};
}
@ -90,7 +90,10 @@ export default class AttachableItem extends BaseDataItem {
});
if (effectsToRemove.length > 0) {
await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id));
await actor.deleteEmbeddedDocuments(
'ActiveEffect',
effectsToRemove.map(e => e.id)
);
}
}
@ -140,13 +143,18 @@ export default class AttachableItem extends BaseDataItem {
const parentUuidProperty = `${parentType}Uuid`;
const effectsToRemove = actor.effects.filter(effect => {
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
return attachmentSource &&
return (
attachmentSource &&
attachmentSource[parentUuidProperty] === this.parent.uuid &&
attachmentSource.itemUuid === attachedUuid;
attachmentSource.itemUuid === attachedUuid
);
});
if (effectsToRemove.length > 0) {
await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id));
await actor.deleteEmbeddedDocuments(
'ActiveEffect',
effectsToRemove.map(e => e.id)
);
}
}
}
}

View file

@ -11,12 +11,15 @@
const fields = foundry.data.fields;
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ITEMS'];
/** @returns {ItemDataModelMetadata}*/
static get metadata() {
return {
label: 'Base Item',
type: 'base',
hasDescription: false,
hasResource: false,
isQuantifiable: false,
isInventoryItem: false
};
@ -33,6 +36,33 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
if (this.metadata.hasResource) {
schema.resource = new fields.SchemaField(
{
type: new fields.StringField({
choices: CONFIG.DH.ITEM.itemResourceTypes,
initial: CONFIG.DH.ITEM.itemResourceTypes.simple
}),
value: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
max: new fields.StringField({ nullable: true, initial: null }),
icon: new fields.StringField(),
recovery: new fields.StringField({
choices: CONFIG.DH.GENERAL.refreshTypes,
initial: null,
nullable: true
}),
diceStates: new fields.TypedObjectField(
new fields.SchemaField({
value: new fields.NumberField({ integer: true, nullable: true, initial: null }),
used: new fields.BooleanField({ initial: false })
})
),
dieFaces: new fields.StringField({ initial: '4' })
},
{ nullable: true, initial: null }
);
}
if (this.metadata.isQuantifiable)
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
@ -62,28 +92,27 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
return data;
}
/**@inheritdoc */
async _preCreate(data, options, user) {
// Skip if no initial action is required or actions already exist
if (!this.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return;
if (this.metadata.hasInitialAction && foundry.utils.isEmpty(this.actions)) {
const metadataType = this.metadata.type;
const actionType = { weapon: 'attack' }[metadataType];
const ActionClass = game.system.api.models.actions.actionsTypes[actionType];
const metadataType = this.metadata.type;
const actionType = { weapon: 'attack' }[metadataType];
const ActionClass = game.system.api.models.actions.actionsTypes[actionType];
const action = new ActionClass(
{
_id: foundry.utils.randomID(),
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
...ActionClass.getSourceConfig(this.parent)
},
{
parent: this.parent
}
);
const action = new ActionClass(
{
_id: foundry.utils.randomID(),
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
...ActionClass.getSourceConfig(this.parent)
},
{
parent: this.parent
}
);
this.updateSource({ actions: [action] });
this.updateSource({ actions: [action] });
}
}
_onCreate(data) {
@ -95,7 +124,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
...feature,
system: {
...feature.system,
type: this.parent.type,
originItemType: this.parent.type,
originId: data._id,
identifier: feature.identifier
}

View file

@ -7,7 +7,8 @@ export default class DHDomainCard extends BaseDataItem {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Item.domainCard',
type: 'domainCard',
hasDescription: true
hasDescription: true,
hasResource: true
});
}

View file

@ -7,7 +7,8 @@ export default class DHFeature extends BaseDataItem {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Item.feature',
type: 'feature',
hasDescription: true
hasDescription: true,
hasResource: true
});
}
@ -16,10 +17,33 @@ export default class DHFeature extends BaseDataItem {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
type: new fields.StringField({ choices: CONFIG.DH.ITEM.featureTypes, nullable: true, initial: null }),
originItemType: new fields.StringField({
choices: CONFIG.DH.ITEM.featureTypes,
nullable: true,
initial: null
}),
subType: new fields.StringField({ choices: CONFIG.DH.ITEM.featureSubTypes, nullable: true, initial: null }),
originId: new fields.StringField({ nullable: true, initial: null }),
identifier: new fields.StringField(),
actions: new fields.ArrayField(new ActionField())
};
}
get spellcastingModifier() {
let traitValue = 0;
if (this.actor && this.originId && ['class', 'subclass'].includes(this.originItemType)) {
if (this.originItemType === 'subclass') {
traitValue =
this.actor.system.traits[this.actor.items.get(this.originId).system.spellcastingTrait]?.value ?? 0;
} else {
const subclass =
this.actor.system.multiclass.value?.id === this.originId
? this.actor.system.multiclass.subclass
: this.actor.system.class.subclass;
traitValue = this.actor.system.traits[subclass.system.spellcastingTrait]?.value ?? 0;
}
}
return traitValue;
}
}

View file

@ -9,7 +9,6 @@ export default class DHWeapon extends AttachableItem {
label: 'TYPES.Item.weapon',
type: 'weapon',
hasDescription: true,
isQuantifiable: true,
isInventoryItem: true
// hasInitialAction: true
});
@ -37,7 +36,7 @@ export default class DHWeapon extends AttachableItem {
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
})
),
attack: new ActionField({
attack: new ActionField({
initial: {
name: 'Attack',
img: 'icons/skills/melee/blood-slash-foam-red.webp',

View file

@ -56,7 +56,7 @@ export default class DHRoll extends Roll {
// Create Chat Message
if (config.source?.message) {
if(game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
} else {
config.message = await this.toMessage(roll, config);
}

View file

@ -1,3 +1,5 @@
import { itemAbleRollParse } from '../helpers/utils.mjs';
export default class DhActiveEffect extends ActiveEffect {
get isSuppressed() {
// If this is a copied effect from an attachment, never suppress it
@ -24,16 +26,18 @@ export default class DhActiveEffect extends ActiveEffect {
*/
get isAttached() {
if (!this.parent || !this.parent.parent) return false;
// Check if this item's UUID is in any actor's armor or weapon attachment lists
const actor = this.parent.parent;
if (!actor || !actor.items) return false;
return actor.items.some(item => {
return (item.type === 'armor' || item.type === 'weapon') &&
item.system?.attached &&
Array.isArray(item.system.attached) &&
item.system.attached.includes(this.parent.uuid);
return (
(item.type === 'armor' || item.type === 'weapon') &&
item.system?.attached &&
Array.isArray(item.system.attached) &&
item.system.attached.includes(this.parent.uuid)
);
});
}
@ -51,11 +55,7 @@ export default class DhActiveEffect extends ActiveEffect {
}
static applyField(model, change, field) {
const isItemTarget = change.value.toLowerCase().startsWith('item.');
change.value = isItemTarget ? change.value.slice(5) : change.value;
change.value = Roll.safeEval(
Roll.replaceFormulaData(change.value, isItemTarget ? change.effect.parent : model)
);
change.value = itemAbleRollParse(change.value, model, change.effect.parent);
super.applyField(model, change, field);
}

View file

@ -1,9 +1,8 @@
import DamageSelectionDialog from '../applications/dialogs/damageSelectionDialog.mjs';
import { emitAsGM, emitAsOwner, GMUpdateEvent, socketEvent } from '../systemRegistration/socket.mjs';
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
import { LevelOptionType } from '../data/levelTier.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 {
/**
@ -396,7 +395,7 @@ export default class DhpActor extends Actor {
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null;
if (this.type === 'companion') {
await this.modifyResource([{ value: 1, type: 'stress' }]);
await this.modifyResource([{ value: 1, key: 'stress' }]);
return;
}
@ -418,8 +417,8 @@ export default class DhpActor extends Actor {
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
updates.push(
...(armorSpent ? [{ value: armorSpent, type: 'armorStack' }] : []),
...(stressSpent ? [{ value: stressSpent, type: 'stress' }] : [])
...(armorSpent ? [{ value: armorSpent, key: 'armorStack' }] : []),
...(stressSpent ? [{ value: stressSpent, key: 'stress' }] : [])
);
}
}
@ -434,8 +433,8 @@ export default class DhpActor extends Actor {
/* if(this.system.resistance[type]?.immunity) return 0;
if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */
if(this.canResist(type, 'immunity')) return 0;
if(this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
if (this.canResist(type, 'immunity')) return 0;
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
// const flatReduction = this.system.resistance[type].reduction;
const flatReduction = this.getDamageTypeReduction(type);
@ -448,13 +447,16 @@ export default class DhpActor extends Actor {
}
canResist(type, resistance) {
if(!type) return 0;
if (!type) return 0;
return type.every(t => this.system.resistance[t]?.[resistance] === true);
}
getDamageTypeReduction(type) {
if(!type) return 0;
const reduction = Object.entries(this.system.resistance).reduce((a, [index, value]) => type.includes(index) ? Math.min(value.reduction, a) : a, Infinity);
if (!type) return 0;
const reduction = Object.entries(this.system.resistance).reduce(
(a, [index, value]) => (type.includes(index) ? Math.min(value.reduction, a) : a),
Infinity
);
return reduction === Infinity ? 0 : reduction;
}
@ -467,39 +469,61 @@ export default class DhpActor extends Actor {
if (!resources.length) return;
if (resources.find(r => r.type === 'stress')) this.convertStressDamageToHP(resources);
let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } };
let updates = {
actor: { target: this, resources: {} },
armor: { target: this.system.armor, resources: {} },
items: {}
};
resources.forEach(r => {
switch (r.type) {
case 'fear':
ui.resources.updateFear(
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + r.value
);
break;
case 'armorStack':
updates.armor.resources['system.marks.value'] = Math.max(
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
0
);
break;
default:
updates.actor.resources[`system.resources.${r.type}.value`] = Math.max(
Math.min(
this.system.resources[r.type].value + r.value,
this.system.resources[r.type].max
),
0
);
break;
if (r.keyIsID) {
updates.items[r.key] = {
target: r.target,
resources: {
'system.resource.value': r.target.system.resource.value + r.value
}
};
} else {
switch (r.key) {
case 'fear':
ui.resources.updateFear(
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) + r.value
);
break;
case 'armorStack':
updates.armor.resources['system.marks.value'] = Math.max(
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
0
);
break;
default:
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
Math.min(this.system.resources[r.key].value + r.value, this.system.resources[r.key].max),
0
);
break;
}
}
});
Object.values(updates).forEach(async u => {
if (Object.keys(u.resources).length > 0) {
await emitAsGM(
GMUpdateEvent.UpdateDocument,
u.target.update.bind(u.target),
u.resources,
u.target.uuid
);
Object.keys(updates).forEach(async key => {
const u = updates[key];
if (key === 'items') {
Object.values(u).forEach(async item => {
await emitAsGM(
GMUpdateEvent.UpdateDocument,
item.target.update.bind(item.target),
item.resources,
item.target.uuid
);
});
} else {
if (Object.keys(u.resources).length > 0) {
await emitAsGM(
GMUpdateEvent.UpdateDocument,
u.target.update.bind(u.target),
u.resources,
u.target.uuid
);
}
}
});
}

View file

@ -1,3 +1,5 @@
import { itemAbleRollParse } from './utils.mjs';
export default class RegisterHandlebarsHelpers {
static registerHelpers() {
Handlebars.registerHelper({
@ -6,7 +8,7 @@ export default class RegisterHandlebarsHelpers {
times: this.times,
damageFormula: this.damageFormula,
damageSymbols: this.damageSymbols,
tertiary: this.tertiary
rollParsed: this.rollParsed
});
}
@ -42,7 +44,9 @@ export default class RegisterHandlebarsHelpers {
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
}
static tertiary(a, b) {
return a ?? b;
static rollParsed(value, actor, item, numerical) {
const isNumerical = typeof numerical === 'boolean' ? numerical : false;
const result = itemAbleRollParse(value, actor, item);
return isNumerical && !result ? 0 : result;
}
}

View file

@ -245,10 +245,10 @@ export const getDamageLabel = damage => {
export const damageKeyToNumber = key => {
return {
'none': 0,
'minor': 1,
'major': 2,
'severe': 3
none: 0,
minor: 1,
major: 2,
severe: 3
}[key];
};
@ -311,3 +311,15 @@ export function getDocFromElement(element) {
return foundry.utils.fromUuidSync(target.dataset.itemUuid) ?? null;
}
export const itemAbleRollParse = (value, actor, item) => {
if (!value) return value;
const isItemTarget = value.toLowerCase().startsWith('item.');
const slicedValue = isItemTarget ? value.slice(5) : value;
try {
return Roll.safeEval(Roll.replaceFormulaData(slicedValue, isItemTarget ? item : actor));
} catch (_) {
return '';
}
};

View file

@ -9,6 +9,8 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/sheets/global/partials/action-item.hbs',
'systems/daggerheart/templates/sheets/global/partials/domain-card-item.hbs',
'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items.hbs',
'systems/daggerheart/templates/sheets/global/partials/item-resource.hbs',
'systems/daggerheart/templates/sheets/global/partials/resource-section.hbs',
'systems/daggerheart/templates/components/card-preview.hbs',
'systems/daggerheart/templates/levelup/parts/selectable-card-preview.hbs',
'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs',

View file

@ -1,11 +1,111 @@
@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 {
.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 {
display: flex;
flex-direction: column;
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 {
width: 140px;
display: flex;
@ -15,6 +115,43 @@
.card-preview-container {
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 {
@ -27,6 +164,10 @@
border-radius: 8px;
border-color: light-dark(@dark-blue, @golden);
&.inactive {
opacity: 0.2;
}
legend {
margin-left: auto;
margin-right: auto;
@ -44,6 +185,7 @@
legend {
font-size: 20px;
white-space: nowrap;
}
.action-button {
@ -87,21 +229,38 @@
}
.traits-inner-container {
width: 100%;
display: flex;
align-items: center;
justify-content: space-evenly;
gap: 8px;
.trait-container {
border: 1px solid light-dark(@dark-blue, @golden);
padding: 0 4px;
width: 60px;
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 {
display: flex;
justify-content: space-evenly;
text-align: center;
flex-direction: column;
gap: 8px;
.experience-container {
position: relative;

View file

@ -4,6 +4,8 @@
@import './level-up/summary-container.less';
@import './level-up/tiers-container.less';
@import './resource-dice/sheet.less';
@import './actions/action-list.less';
@import './damage-selection/sheet.less';

View file

@ -0,0 +1,58 @@
.theme-light .daggerheart.dialog.dh-style.views.resource-dice {
.resource-items .resource-item {
input {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
img {
filter: brightness(0) saturate(100%);
}
}
}
.daggerheart.dialog.dh-style.views.resource-dice {
.reroll-confirmation {
margin-bottom: 8px;
}
.resource-items {
display: flex;
justify-content: center;
gap: 8px;
.resource-item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
input {
position: absolute;
border-color: light-dark(@dark-blue, @golden);
color: light-dark(black, white);
background-image: url('../assets/parchments/dh-parchment-dark.png');
z-index: 2;
line-height: 22px;
height: unset;
text-align: center;
}
img {
width: 48px;
height: 48px;
filter: brightness(0) saturate(100%) invert(97%) sepia(7%) saturate(580%) hue-rotate(332deg)
brightness(96%) contrast(95%);
}
}
}
footer {
display: flex;
gap: 8px;
button {
flex: 1;
white-space: nowrap;
}
}
}

View file

@ -112,7 +112,7 @@
margin: 5px;
height: inherit;
.tag {
box-shadow: 0 0 0 1.1em #E5E5E5 inset;
box-shadow: 0 0 0 1.1em @beige inset;
vertical-align: top;
box-sizing: border-box;
max-width: 100%;
@ -120,9 +120,9 @@
color: black;
border-radius: 3px;
white-space: nowrap;
transition: .13s ease-out;
transition: 0.13s ease-out;
height: 22px;
font-size: .9rem;
font-size: 0.9rem;
gap: 0.5em;
z-index: 1;
.remove {

View file

@ -6,6 +6,7 @@
@import './tab-actions.less';
@import './tab-features.less';
@import './tab-effects.less';
@import './tab-settings.less';
@import './item-header.less';
@import './feature-section.less';
@import './inventory-item.less';

View file

@ -1,6 +1,17 @@
@import '../utils/colors.less';
@import '../utils/fonts.less';
.theme-light .application.daggerheart.dh-style {
.inventory-item,
.card-item {
.item-resource .item-dice-resource {
img {
filter: brightness(0) saturate(100%);
}
}
}
}
.application.daggerheart.dh-style {
.inventory-item {
display: grid;
@ -21,10 +32,19 @@
}
}
.item-label-wrapper {
display: grid;
grid-template-columns: 1fr 60px;
}
.item-label {
font-family: @font-body;
align-self: center;
&.fullWidth {
grid-column: span 2;
}
.item-name {
font-size: 14px;
}
@ -84,11 +104,27 @@
&:hover {
.card-label {
padding-top: 15px;
.controls {
.menu {
opacity: 1;
visibility: visible;
transition: all 0.3s ease;
max-height: 16px;
&.resource-menu {
max-height: 55px;
&.dice-menu {
max-height: 118px;
.item-resources {
flex-wrap: wrap;
}
.item-resource {
width: unset;
}
}
}
}
}
}
@ -97,6 +133,7 @@
height: 100%;
width: 100%;
object-fit: cover;
border-radius: 6px;
}
.card-label {
@ -123,15 +160,88 @@
color: @beige;
}
.controls {
.menu {
display: flex;
gap: 15px;
align-items: center;
flex-direction: column;
gap: 8px;
max-height: 0px;
opacity: 0;
visibility: collapse;
transition: all 0.3s ease;
color: @beige;
.controls {
display: flex;
gap: 2px;
gap: 15px;
justify-content: center;
}
}
}
.item-resources {
width: 92px;
}
.item-resource {
width: 92px;
}
}
.inventory-item,
.card-item {
.item-resources {
display: flex;
gap: 4px;
.resource-edit {
font-size: 14px;
}
}
.item-resource {
display: flex;
align-items: center;
justify-content: end;
gap: 4px;
i {
flex: none;
font-size: 14px;
}
input {
flex: 1;
}
.item-dice-resource {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 26px;
label {
position: absolute;
color: light-dark(white, black);
filter: drop-shadow(0 0 1px light-dark(@dark-blue, @golden));
z-index: 2;
font-size: 18px;
cursor: pointer;
}
img {
filter: brightness(0) saturate(100%) invert(97%) sepia(7%) saturate(580%) hue-rotate(332deg)
brightness(96%) contrast(95%);
}
i {
position: absolute;
text-shadow: 0 0 3px white;
filter: drop-shadow(0 1px white);
color: black;
font-size: 26px;
}
}
}
}

View file

@ -4,4 +4,4 @@
width: 100%;
}
}
}
}

View file

@ -0,0 +1,8 @@
@import '../utils/colors.less';
@import '../utils/fonts.less';
.sheet.daggerheart.dh-style {
.tab.settings {
margin-bottom: 36px;
}
}

View file

@ -31,6 +31,14 @@
}
}
&.resource-roll {
.reroll-message {
text-align: center;
font-size: 18px;
margin-bottom: 0;
}
}
&.roll {
.dice-flavor {
text-align: center;

View file

@ -6,7 +6,7 @@
{{#each source as |cost index|}}
<div class="nest-inputs">
{{formField ../fields.scalable label="Scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox"}}
{{formField ../fields.type choices=(@root.disableOption index ../fields.type.choices ../source) label="Resource" value=cost.type name=(concat "cost." index ".type") localize=true}}
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="Resource" value=cost.key name=(concat "cost." index ".key") localize=true}}
{{formField ../fields.value label="Amount" value=cost.value name=(concat "cost." index ".value")}}
{{formField ../fields.step label="Step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable)}}
<a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>

View file

@ -1,4 +1,8 @@
<section class="creation-action-footer">
<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>

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-group='{{tabs.setup.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>
{{#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}}
</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>
{{/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>
<nav class='feature-tab sheet-tabs tabs setup-tabs' data-group='setup'>
{{#each setupTabs as |tab|}}
<button class='{{tab.id}} {{tab.cssClass}}' data-action='tab' data-group='{{tab.group}}' data-tab='{{tab.id}}' {{disabled tab.disabled}}>
{{localize tab.label}}
</button>
{{/each}}
</nav>
</section>

View file

@ -4,7 +4,13 @@
>
{{#if 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}}
<div class="preview-empty-container">
<div class="preview-empty-inner-container">

View file

@ -10,7 +10,7 @@
{{#each costs as | cost index |}}
<div class="form-group">
<div class="form-fields">
<label for="{{type}}">{{type}}: {{total}}</label>
<label>{{label}}: {{total}}</label>
<input name="costs.{{index}}.enabled" type="checkbox"{{#if enabled}} checked{{/if}}>
{{#if scalable}}
<input type="range" value="{{scale}}" min="1" max="10" step="{{step}}" name="costs.{{index}}.scale">

View file

@ -0,0 +1,16 @@
<section>
<div class="resource-items">
{{#times (rollParsed item.system.resource.max actor item numerical=true)}}
{{#with (ifThen (lookup ../diceStates this) (lookup ../diceStates this) this) as | state |}}
<div class="resource-item" data-dice="{{#if ../../this}}{{../this}}{{else}}{{state}}{{/if}}">
<input type="number" data-dtype="Number" name={{concat "diceStates." (ifThen ../../this ../this state) ".value" }} value="{{state.value}}" />
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/d" (ifThen ../../item.system.resource.dieFaces ../../item.system.resource.dieFaces ../item.system.resource.dieFaces) ".svg"}}" />
</div>
{{/with}}
{{/times}}
</div>
<footer>
<button data-action="save">{{localize 'Save'}}</button>
<button data-action="rerollDice">{{localize "DAGGERHEART.APPLICATIONS.ResourceDice.rerollDice"}}</button>
</footer>
</section>

View file

@ -4,6 +4,6 @@
data-tab="config"
>
{{> 'systems/daggerheart/templates/actionTypes/uses.hbs' fields=fields.uses.fields source=source.uses}}
{{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost}}
{{> 'systems/daggerheart/templates/actionTypes/cost.hbs' fields=fields.cost.element.fields source=source.cost costOptions=costOptions}}
{{> 'systems/daggerheart/templates/actionTypes/range-target.hbs' fields=(object range=fields.range target=fields.target.fields) source=(object target=source.target range=source.range)}}
</section>

View file

@ -15,7 +15,7 @@
{{#if (or document.system.needsCharacterSetup document.system.levelData.canLevelUp)}}
<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"
>
<i class="fa-solid fa-triangle-exclamation"></i>

View file

@ -1,128 +1,137 @@
<li class="inventory-item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-type="{{type}}" draggable="true">
<img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" data-action="useItem" {{#if (not noTooltip)}}data-tooltip="{{concat "#item#" item.uuid}}"{{/if}} />
<div class="item-label">
{{#if isCompanion}}
<a class="item-name" data-action="attackRoll">{{item.name}}</a>
{{else}}
<div class="item-name">{{item.name}}</div>
{{/if}}
{{#if (eq type 'weapon')}}
<div class="item-tags">
<div class="item-label-wrapper">
<div class="item-label {{#unless (and (not isSidebar) (or (eq item.system.resource.type 'simple') item.system.quantity))}}fullWidth{{/unless}}">
{{#if isCompanion}}
<a class="item-name" data-action="attackRoll">{{item.name}}</a>
{{else}}
<div class="item-name">{{item.name}}</div>
{{/if}}
{{#if (eq type 'weapon')}}
<div class="item-tags">
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' item.system.attack.roll.trait '.short')}}
{{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.short')}}
<span> - </span>
{{item.system.attack.damage.parts.0.value.dice}}{{#if item.system.attack.damage.parts.0.value.bonus}} + {{item.system.attack.damage.parts.0.value.bonus}}{{/if}}
{{#each item.system.attack.damage.parts.0.type as | type | }}
{{#with (lookup @root.config.GENERAL.damageTypes type)}}
<i class="fa-solid {{icon}}"></i>
{{/with}}
{{/each}}
</div>
</div>
{{else}}
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' item.system.attack.roll.trait '.name')}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.name')}}
</div>
<div class="tag">
{{item.system.attack.damage.parts.0.value.dice}}{{#if item.system.attack.damage.parts.0.value.bonus}} + {{item.system.attack.damage.parts.0.value.bonus}}{{/if}}
(
{{#each item.system.attack.damage.parts.0.type}}
{{localize (concat 'DAGGERHEART.CONFIG.DamageType.' this '.abbreviation')}}
{{/each}}
)
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Burden.' item.system.burden)}}
</div>
{{/if}}
</div>
{{/if}}
{{#if (eq type 'armor')}}
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{!-- {{localize (concat 'DAGGERHEART.CONFIG.Traits.' item.system.attack.roll.trait '.short')}} --}}
{{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.short')}}
<span> - </span>
{{item.system.attack.damage.parts.0.value.dice}}{{#if item.system.attack.damage.parts.0.value.bonus}} + {{item.system.attack.damage.parts.0.value.bonus}}{{/if}}
{{!-- ({{localize (concat 'DAGGERHEART.CONFIG.DamageType.' item.system.attack.damage.parts.0.type '.abbreviation')}}) --}}
{{#each item.system.attack.damage.parts.0.type as | type | }}
{{#with (lookup @root.config.GENERAL.damageTypes type)}}
<i class="fa-solid {{icon}}"></i>
{{/with}}
{{/each}}
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{item.system.baseScore}}
</div>
</div>
{{else}}
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' item.system.attack.roll.trait '.name')}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.name')}}
</div>
<div class="tag">
{{item.system.attack.damage.parts.0.value.dice}}{{#if item.system.attack.damage.parts.0.value.bonus}} + {{item.system.attack.damage.parts.0.value.bonus}}{{/if}}
(
{{#each item.system.attack.damage.parts.0.type}}
{{localize (concat 'DAGGERHEART.CONFIG.DamageType.' this '.abbreviation')}}
{{/each}}
)
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Burden.' item.system.burden)}}
<div class="item-tags">
<div class="tag">
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{item.system.baseScore}}
</div>
<div class="tag">
{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}:
{{item.system.baseThresholds.major}}
<span>/</span>
{{item.system.baseThresholds.severe}}
</div>
</div>
{{/if}}
</div>
{{/if}}
{{#if (eq type 'armor')}}
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{item.system.baseScore}}
{{/if}}
{{#if (eq type 'domainCard')}}
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
<span> - </span>
{{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
<span> - </span>
<span class="recall-value">{{item.system.recallCost}}</span>
<i class="fa-solid fa-bolt"></i>
</div>
</div>
</div>
{{else}}
{{else}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
</div>
<div class="tag">
<span class="recall-label">{{localize "DAGGERHEART.ITEMS.DomainCard.recallCost"}}: </span>
<span class="recall-value">{{item.system.recallCost}}</span>
</div>
</div>
{{/if}}
{{/if}}
{{#if (eq type 'effect')}}
<div class="item-tags">
<div class="tag">
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{item.system.baseScore}}
{{localize (concat 'TYPES.Item.' item.parent.type)}}
<span>: </span>
{{item.parent.name}}
</div>
<div class="tag">
{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}:
{{item.system.baseThresholds.major}}
<span>/</span>
{{item.system.baseThresholds.severe}}
{{#if item.duration.duration}}
{{localize 'DAGGERHEART.EFFECTS.Duration.temporary'}}
{{else}}
{{localize 'DAGGERHEART.EFFECTS.Duration.passive'}}
{{/if}}
</div>
{{#each item.statuses as |status|}}
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Condition.' status '.name')}}
</div>
{{/each}}
</div>
{{/if}}
{{#if (eq type 'action')}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'DAGGERHEART.ACTIONS.TYPES.' item.type '.name')}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.ActionType.' item.actionType)}}
</div>
</div>
{{/if}}
</div>
{{#if (and (not isSidebar) (eq item.system.resource.type 'simple'))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}}
{{#if (eq type 'domainCard')}}
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
<span> - </span>
{{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
<span> - </span>
<span class="recall-value">{{item.system.recallCost}}</span>
<i class="fa-solid fa-bolt"></i>
</div>
</div>
{{else}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
</div>
<div class="tag">
<span class="recall-label">{{localize "DAGGERHEART.ITEMS.DomainCard.recallCost"}}: </span>
<span class="recall-value">{{item.system.recallCost}}</span>
</div>
</div>
{{/if}}
{{/if}}
{{#if (eq type 'effect')}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'TYPES.Item.' item.parent.type)}}
<span>: </span>
{{item.parent.name}}
</div>
<div class="tag">
{{#if item.duration.duration}}
{{localize 'DAGGERHEART.EFFECTS.Duration.temporary'}}
{{else}}
{{localize 'DAGGERHEART.EFFECTS.Duration.passive'}}
{{/if}}
</div>
{{#each item.statuses as |status|}}
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Condition.' status '.name')}}
</div>
{{/each}}
</div>
{{/if}}
{{#if (eq type 'action')}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'DAGGERHEART.ACTIONS.TYPES.' item.type '.name')}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.ActionType.' item.actionType)}}
</div>
{{#if (and (not isSidebar) item.system.quantity)}}
<div class="item-resource">
<input type="number" class="inventory-item-quantity" value="{{item.system.quantity}}" step="1" />
</div>
{{/if}}
</div>
@ -175,7 +184,9 @@
<span></span>
{{/unless}}
<div class="item-description">{{#unless isSidebar}}{{{item.system.description}}}{{/unless}}</div>
{{#if (and (not isSidebar) (eq item.system.resource.type 'diceValue'))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}}
{{#if featureType}}
<div class="item-buttons">
{{#each item.system.actions as | action |}}

View file

@ -0,0 +1,21 @@
{{#if (eq item.system.resource.type 'simple')}}
<div class="item-resource">
<i class="{{#if item.system.resource.icon}}{{item.system.resource.icon}}{{else}}fa-solid fa-hashtag{{/if}}"></i>
<input type="number" class="inventory-item-resource" value="{{item.system.resource.value}}" step="1" />
</div>
{{else}}
<div class="item-resources">
{{#times (rollParsed item.system.resource.max item.parent item numerical=true)}}
{{#with (ifThen (lookup ../item.system.resource.diceStates this) (lookup ../item.system.resource.diceStates this) this) as | state |}}
<a class="item-resource" data-action="toggleResourceDice" data-dice="{{#if ../../this}}{{../this}}{{else}}{{state}}{{/if}}">
<div class="item-dice-resource">
<label>{{ifThen state.value state.value '?'}}</label>
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/d" (ifThen ../../item.system.resource.dieFaces ../../item.system.resource.dieFaces ../item.system.resource.dieFaces) ".svg"}}" />
{{#if state.used}}<i class="fa-solid fa-x"></i>{{/if}}
</div>
</a>
{{/with}}
{{/times}}
<a data-action="handleResourceDice" data-tooltip="DAGGERHEART.APPLICATIONS.ResourceDice.rerollDice"><i class="fa-solid fa-dice resource-edit"></i></a>
</div>
{{/if}}

View file

@ -0,0 +1,29 @@
<fieldset>
<legend>
{{localize "DAGGERHEART.GENERAL.Resource.single"}}
{{#unless source.system.resource}}
<a data-action="addResource"><i class="fa-solid fa-plus icon-button"></i></a>
{{else}}
<a data-action="removeResource"><i class="fa-solid fa-trash"></i></a>
{{/unless}}
</legend>
{{#if source.system.resource}}
<div class="two-columns even">
{{formGroup systemFields.resource.fields.type value=source.system.resource.type localize=true blank=false}}
{{formGroup systemFields.resource.fields.recovery value=source.system.resource.recovery localize=true}}
</div>
<div class="two-columns even">
{{#if (eq source.system.resource.type 'simple')}}
{{formGroup systemFields.resource.fields.value value=source.system.resource.value localize=true}}
{{formGroup systemFields.resource.fields.max value=source.system.resource.max localize=true}}
{{else}}
{{formGroup systemFields.resource.fields.dieFaces value=source.system.resource.dieFaces localize=true}}
{{formGroup systemFields.resource.fields.max value=source.system.resource.max label="DAGGERHEART.ITEMS.FIELDS.resource.amount.label" localize=true}}
{{/if}}
</div>
{{#if (eq source.system.resource.type 'simple')}}{{formGroup systemFields.resource.fields.icon value=source.system.resource.icon localize=true placeholder="fa-solid fa-hashtag"}}{{/if}}
{{/if}}
</fieldset>

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>

View file

@ -1,7 +1,7 @@
<section class='tab {{tabs.features.cssClass}} {{tabs.features.id}}' data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}'>
<div class="two-columns even">
<fieldset>
<fieldset class="drop-section hope-feature">
<legend>
{{localize "DAGGERHEART.ITEMS.Class.hopeFeatures"}}
<a data-action-path="hopeFeatures" data-action="addFeature">
@ -16,7 +16,7 @@
</div>
</fieldset>
<fieldset>
<fieldset class="drop-section class-feature">
<legend>
{{localize "DAGGERHEART.ITEMS.Class.classFeatures"}}
<a data-action-path="classFeatures" data-action="addFeature">

View file

@ -17,4 +17,6 @@
<span>{{localize "DAGGERHEART.ITEMS.DomainCard.recallCost"}}</span>
{{formField systemFields.recallCost value=source.system.recallCost data-dtype="Number"}}
</fieldset>
{{> "systems/daggerheart/templates/sheets/global/partials/resource-section.hbs" }}
</section>

View file

@ -0,0 +1,7 @@
<section
class='tab {{tabs.settings.cssClass}} {{tabs.settings.id}}'
data-tab='{{tabs.settings.id}}'
data-group='{{tabs.settings.group}}'
>
{{> "systems/daggerheart/templates/sheets/global/partials/resource-section.hbs" }}
</section>

View file

@ -0,0 +1,3 @@
<div class="daggerheart chat resource-roll">
<h5 class="reroll-message">{{localize "DAGGERHEART.UI.Chat.resourceRoll.playerMessage" user=user name=name }}</h5>
</div>

View file

@ -6,7 +6,7 @@
<div class="tooltip-tag-label">{{localize feature.name}}</div>
{{#if feature.img}}<img class="tooltip-tag-image" src="{{feature.img}}" />{{/if}}
</div>
<div class="tooltip-tag-description">{{{localize (tertiary feature.description feature.system.description)}}}</div>
<div class="tooltip-tag-description">{{{localize (ifThen feature.description feature.description feature.system.description)}}}</div>
</div>
{{/each}}
</div>