Merged with development

This commit is contained in:
WBHarry 2025-09-07 01:04:11 +02:00
commit aed7942fc4
69 changed files with 1092 additions and 229 deletions

View file

@ -1,5 +1,6 @@
import { SYSTEM } from './module/config/system.mjs';
import * as applications from './module/applications/_module.mjs';
import * as data from './module/data/_module.mjs';
import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs';
import * as dice from './module/dice/_module.mjs';
@ -26,6 +27,7 @@ Hooks.once('init', () => {
CONFIG.DH = SYSTEM;
game.system.api = {
applications,
data,
models,
documents,
dice,
@ -133,6 +135,8 @@ Hooks.once('init', () => {
CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.ui.resources = applications.ui.DhFearTracker;

View file

@ -501,18 +501,21 @@
},
"takeLevelUp": "Finish Level Up",
"tier2": {
"name": "Tier 2",
"label": "Levels 2-4",
"infoLabel": "At Level 2, gain an additional Experience at +2 and gain a +1 bonus to your Proficiency.",
"pretext": "Choose two options from the list below",
"posttext": "Take an additional domain card of your level or lower from a domain you have access to."
},
"tier3": {
"name": "Tier 3",
"label": "Levels 5-7",
"infoLabel": "At Level 5, take an additional Experience and clear all marks on Character Traits.",
"pretext": "When you level up, record it on your character sheet, then choose two from the list below or any unmarked from the previous tier.",
"posttext": "Take an additional domain card of your level or lower from a domain you have access to."
},
"tier4": {
"name": "Tier 4",
"label": "Levels 8-10",
"infoLabel": "At Level 8, take an additional Experience and clear all marks on Character Traits.",
"pretext": "When you level up, record it on your character sheet, then choose two from the list below or any unmarked from the previous tier.",
@ -1026,6 +1029,12 @@
"selectType": "Select Action Type",
"selectAction": "Action Selection"
},
"TargetTypes": {
"any": "Any",
"friendly": "Friendly",
"hostile": "Hostile",
"self": "Self"
},
"TemplateTypes": {
"circle": "Circle",
"cone": "Cone",
@ -1917,7 +1926,10 @@
"roll": "Roll",
"rules": "Rules",
"types": "Types",
"questions": "Questions"
"itemFeatures": "Item Features",
"questions": "Questions",
"configuration": "Configuration",
"base": "Base"
},
"Tiers": {
"singular": "Tier",
@ -1934,6 +1946,7 @@
"amount": "Amount",
"any": "Any",
"armor": "Armor",
"armorFeatures": "Armor Features",
"armors": "Armors",
"armorScore": "Armor Score",
"activeEffects": "Active Effects",
@ -2026,6 +2039,8 @@
"save": "Save",
"scalable": "Scalable",
"situationalBonus": "Situational Bonus",
"spent": "Spent",
"step": "Step",
"stress": "Stress",
"subclasses": "Subclasses",
"success": "Success",
@ -2046,6 +2061,7 @@
"used": "Used",
"uses": "Uses",
"value": "Value",
"weaponFeatures": "Weapon Features",
"weapons": "Weapons",
"withThing": "With {thing}"
},
@ -2276,7 +2292,9 @@
},
"Homebrew": {
"newDowntimeMove": "Downtime Move",
"newFeature": "New ItemFeature",
"downtimeMoves": "Downtime Moves",
"itemFeatures": "Item Features",
"nrChoices": "# Moves Per Rest",
"resetMovesTitle": "Reset {type} Downtime Moves",
"resetMovesText": "Are you sure you want to reset?",
@ -2430,6 +2448,10 @@
"applyHealing": "Apply Healing"
},
"markResource": "Mark {quantity} {resource}",
"refreshMessage": {
"title": "Feature Refresh",
"header": "Refreshed"
},
"reroll": {
"confirmTitle": "Reroll Dice",
"confirmText": "Are you sure you want to reroll?"
@ -2544,8 +2566,16 @@
"multiclassAlreadyPresent": "You already have a class and multiclass",
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
"gmMenuRefresh": "You refreshed all actions and resources {types}",
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class."
},
"Sidebar": {
"daggerheartMenu": {
"title": "Daggerheart Menu",
"startSession": "Start Session",
"startScene": "Start Scene"
}
},
"Tooltip": {
"disableEffect": "Disable Effect",
"enableEffect": "Enable Effect",

View file

@ -6,5 +6,6 @@ export * as scene from './scene/_module.mjs';
export * as settings from './settings/_module.mjs';
export * as sheets from './sheets/_module.mjs';
export * as sheetConfigs from './sheets-configs/_module.mjs';
export * as sidebar from './sidebar/_module.mjs';
export * as ui from './ui/_module.mjs';
export * as ux from './ux/_module.mjs';

View file

@ -1 +1 @@
export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs';
export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs';

View file

@ -1,25 +1,24 @@
export default class DhSceneConfigSettings extends foundry.applications.sheets.SceneConfig {
constructor(options, ...args) {
super(options, ...args);
}
static buildParts() {
const { footer, ...parts } = super.PARTS;
const tmpParts = {
...parts,
dh: { template: "systems/daggerheart/templates/scene/dh-config.hbs" },
footer
constructor(options, ...args) {
super(options, ...args);
}
return tmpParts;
}
static PARTS = DhSceneConfigSettings.buildParts();
static buildParts() {
const { footer, ...parts } = super.PARTS;
const tmpParts = {
...parts,
dh: { template: 'systems/daggerheart/templates/scene/dh-config.hbs' },
footer
};
return tmpParts;
}
static buildTabs() {
super.TABS.sheet.tabs.push({ id: "dh", icon: "fa-solid" });
return super.TABS;
}
static PARTS = DhSceneConfigSettings.buildParts();
static TABS = DhSceneConfigSettings.buildTabs();
static buildTabs() {
super.TABS.sheet.tabs.push({ id: 'dh', icon: 'fa-solid' });
return super.TABS;
}
}
static TABS = DhSceneConfigSettings.buildTabs();
}

View file

@ -53,6 +53,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.hbs' },
itemTypes: { template: 'systems/daggerheart/templates/settings/homebrew-settings/itemFeatures.hbs' },
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
};
@ -60,7 +61,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
/** @inheritdoc */
static TABS = {
main: {
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'downtime' }],
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
initial: 'settings',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
@ -115,33 +116,53 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
}
static async addItem(_, target) {
await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: []
}
});
const { type } = target.dataset;
if (['shortRest', 'longRest'].includes(type)) {
await this.settings.updateSource({
[`restMoves.${type}.moves.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newDowntimeMove'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: []
}
});
} else if (['armorFeatures', 'weaponFeatures'].includes(type)) {
await this.settings.updateSource({
[`itemFeatures.${type}.${foundry.utils.randomID()}`]: {
name: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.newFeature'),
img: 'icons/magic/life/cross-worn-green.webp',
description: '',
actions: [],
effects: []
}
});
}
this.render();
}
static async editItem(_, target) {
const move = this.settings.restMoves[target.dataset.type].moves[target.dataset.id];
const path = `restMoves.${target.dataset.type}.moves.${target.dataset.id}`;
const editedMove = await game.system.api.applications.sheetConfigs.DowntimeConfig.configure(
move,
path,
this.settings
);
if (!editedMove) return;
const { type, id } = target.dataset;
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves.${id}` : `itemFeatures.${type}.${id}`;
const featureBase = isDowntime ? this.settings.restMoves[type].moves[id] : this.settings.itemFeatures[type][id];
await this.updateAction.bind(this)(editedMove, target.dataset.type, target.dataset.id);
const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure(
featureBase,
path,
this.settings,
{ hasIcon: isDowntime, hasEffects: !isDowntime }
);
if (!editedBase) return;
await this.updateAction.bind(this)(editedBase, target.dataset.type, target.dataset.id);
}
async updateAction(data, type, id) {
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
await this.settings.updateSource({
[`restMoves.${type}.moves.${id}`]: {
[`${path}.${id}`]: {
actions: data.actions,
name: data.name,
icon: data.icon,
@ -149,12 +170,16 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
description: data.description
}
});
this.render();
}
static async removeItem(_, target) {
const { type, id } = target.dataset;
const isDowntime = ['shortRest', 'longRest'].includes(type);
const path = isDowntime ? `restMoves.${type}.moves` : `itemFeatures.${type}`;
await this.settings.updateSource({
[`restMoves.${target.dataset.type}.moves.-=${target.dataset.id}`]: null
[`${path}.-=${id}`]: null
});
this.render();
}

View file

@ -2,7 +2,8 @@ export { default as ActionConfig } from './action-config.mjs';
export { default as CharacterSettings } from './character-settings.mjs';
export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-settings.mjs';
export { default as DowntimeConfig } from './downtimeConfig.mjs';
export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs';
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
export { default as EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';
export { default as DhTokenConfig } from './token-config.mjs';

View file

@ -66,7 +66,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
group: 'primary',
id: 'base',
icon: null,
label: 'Base'
label: 'DAGGERHEART.GENERAL.Tabs.base'
},
config: {
active: false,
@ -74,7 +74,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
group: 'primary',
id: 'config',
icon: null,
label: 'Configuration'
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
},
effect: {
active: false,
@ -82,7 +82,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
group: 'primary',
id: 'effect',
icon: null,
label: 'Effect'
label: 'DAGGERHEART.GENERAL.Tabs.effects'
}
};
@ -138,7 +138,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
};
}
if (this.action.parent.metadata.isQuantifiable) {
if (this.action.parent.metadata?.isQuantifiable) {
options.quantity = {
label: 'DAGGERHEART.GENERAL.itemQuantity',
group: 'Global'

View file

@ -96,6 +96,13 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
});
}
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.systemFields = context.document.system.schema.fields;
return context;
}
async _preparePartContext(partId, context) {
const partContext = await super._preparePartContext(partId, context);
switch (partId) {

View file

@ -0,0 +1,227 @@
import autocomplete from 'autocompleter';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(effect) {
super({});
this.effect = foundry.utils.deepClone(effect);
const ignoredActorKeys = ['config', 'DhEnvironment'];
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
if (!ignoredActorKeys.includes(key)) {
const model = game.system.api.models.actors[key];
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
const group = game.i18n.localize(model.metadata.label);
const choices = CONFIG.Token.documentClass
.getTrackedAttributeChoices(attributes, model)
.map(x => ({ ...x, group: group }));
acc.push(...choices);
}
return acc;
}, []);
}
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config'],
tag: 'form',
position: {
width: 560
},
form: {
submitOnChange: false,
closeOnSubmit: false,
handler: SettingActiveEffectConfig.#onSubmit
},
actions: {
editImage: SettingActiveEffectConfig.#editImage,
addChange: SettingActiveEffectConfig.#addChange,
deleteChange: SettingActiveEffectConfig.#deleteChange
}
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
tabs: { template: 'templates/generic/tab-navigation.hbs' },
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
changes: {
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
scrollable: ['ol[data-changes]']
},
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
};
static TABS = {
sheet: {
tabs: [
{ id: 'details', icon: 'fa-solid fa-book' },
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
{ id: 'changes', icon: 'fa-solid fa-gears' }
],
initial: 'details',
labelPrefix: 'EFFECT.TABS'
}
};
/**@inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.source = this.effect;
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
return context;
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const changeChoices = this.changeChoices;
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
autocomplete({
input: element,
fetch: function (text, update) {
if (!text) {
update(changeChoices);
} else {
text = text.toLowerCase();
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
update(suggestions);
}
},
render: function (item, search) {
const label = game.i18n.localize(item.label);
const matchIndex = label.toLowerCase().indexOf(search);
const beforeText = label.slice(0, matchIndex);
const matchText = label.slice(matchIndex, matchIndex + search.length);
const after = label.slice(matchIndex + search.length, label.length);
const element = document.createElement('li');
element.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
if (item.hint) {
element.dataset.tooltip = game.i18n.localize(item.hint);
}
return element;
},
renderGroup: function (label) {
const itemElement = document.createElement('div');
itemElement.textContent = game.i18n.localize(label);
return itemElement;
},
onSelect: function (item) {
element.value = `system.${item.value}`;
},
click: e => e.fetch(),
customize: function (_input, _inputRect, container) {
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
},
minLength: 0
});
});
}
async _preparePartContext(partId, context) {
if (partId in context.tabs) context.tab = context.tabs[partId];
switch (partId) {
case 'details':
context.isActorEffect = false;
context.isItemEffect = true;
const useGeneric = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects;
if (!useGeneric) {
context.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
value: status.id,
label: game.i18n.localize(status.name)
}));
}
break;
case 'changes':
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
return modes;
}, {});
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
break;
}
return context;
}
static async #onSubmit(event, form, formData) {
this.data = foundry.utils.expandObject(formData.object);
this.close();
}
/**
* Edit a Document image.
* @this {DocumentSheetV2}
* @type {ApplicationClickAction}
*/
static async #editImage(_event, target) {
if (target.nodeName !== 'IMG') {
throw new Error('The editImage action is available only for IMG elements.');
}
const attr = target.dataset.edit;
const current = foundry.utils.getProperty(this.effect, attr);
const fp = new FilePicker.implementation({
current,
type: 'image',
callback: path => (target.src = path),
position: {
top: this.position.top + 40,
left: this.position.left + 10
}
});
await fp.browse();
}
/**
* Add a new change to the effect's changes array.
* @this {ActiveEffectConfig}
* @type {ApplicationClickAction}
*/
static async #addChange() {
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const changes = Object.values(submitData.changes ?? {});
changes.push({});
this.effect.changes = changes;
this.render();
}
/**
* Delete a change from the effect's changes array.
* @this {ActiveEffectConfig}
* @type {ApplicationClickAction}
*/
static async #deleteChange(event) {
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const changes = Object.values(submitData.changes);
const row = event.target.closest('li');
const index = Number(row.dataset.index) || 0;
changes.splice(index, 1);
this.effect.changes = changes;
this.render();
}
static async configure(effect, options = {}) {
return new Promise(resolve => {
const app = new this(effect, options);
app.addEventListener('close', () => resolve(app.data), { once: true });
app.render({ force: true });
});
}
}

View file

@ -3,8 +3,8 @@ import DHActionConfig from './action-config.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DowntimeConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(move, movePath, settings, options) {
export default class SettingFeatureConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(move, movePath, settings, optionalParts, options) {
super(options);
this.move = move;
@ -12,6 +12,10 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
this.movePath = movePath;
this.actionsPath = `${movePath}.actions`;
this.settings = settings;
const { hasIcon, hasEffects } = optionalParts;
this.hasIcon = hasIcon;
this.hasEffects = hasEffects;
}
get title() {
@ -30,6 +34,7 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
addItem: this.addItem,
editItem: this.editItem,
removeItem: this.removeItem,
addEffect: this.addEffect,
resetMoves: this.resetMoves,
saveForm: this.saveForm
},
@ -41,13 +46,14 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
main: { template: 'systems/daggerheart/templates/settings/downtime-config/main.hbs' },
actions: { template: 'systems/daggerheart/templates/settings/downtime-config/actions.hbs' },
effects: { template: 'systems/daggerheart/templates/settings/downtime-config/effects.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/downtime-config/footer.hbs' }
};
/** @inheritdoc */
static TABS = {
primary: {
tabs: [{ id: 'main' }, { id: 'actions' }],
tabs: [{ id: 'main' }, { id: 'actions' }, { id: 'effects' }],
initial: 'main',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
@ -55,6 +61,9 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.tabs = this._filterTabs(context.tabs);
context.hasIcon = this.hasIcon;
context.hasEffects = this.hasEffects;
context.move = this.move;
context.move.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(
context.move.description
@ -130,13 +139,30 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
}
static async editItem(_, target) {
const actionId = target.dataset.id;
const action = this.move.actions.get(actionId);
await new DHActionConfig(action, async updatedMove => {
await this.settings.updateSource({ [`${this.actionsPath}.${actionId}`]: updatedMove });
const { type, id } = target.dataset;
if (type === 'effect') {
const effectIndex = this.move.effects.findIndex(x => x.id === id);
const effect = this.move.effects[effectIndex];
const updatedEffect =
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
if (!updatedEffect) return;
await this.settings.updateSource({
[`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => {
acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect);
return acc;
}, [])
});
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}).render(true);
} else {
const action = this.move.actions.get(id);
await new DHActionConfig(action, async updatedMove => {
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}).render(true);
}
}
static async removeItem(_, target) {
@ -145,16 +171,38 @@ export default class DowntimeConfig extends HandlebarsApplicationMixin(Applicati
this.render();
}
static async addEffect(_, target) {
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
await this.settings.updateSource({
[`${this.movePath}.effects`]: [
...currentEffects,
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
]
});
this.move = foundry.utils.getProperty(this.settings, this.movePath);
this.render();
}
static resetMoves() {}
_filterTabs(tabs) {
return this.hasEffects
? tabs
: Object.keys(tabs).reduce((acc, key) => {
if (key !== 'effects') acc[key] = tabs[key];
return acc;
}, {});
}
/** @override */
_onClose(options = {}) {
if (!options.submitted) this.move = null;
}
static async configure(move, movePath, settings, options = {}) {
static async configure(move, movePath, settings, optionalParts, options = {}) {
return new Promise(resolve => {
const app = new this(move, movePath, settings, options);
const app = new this(move, movePath, settings, optionalParts, options);
app.addEventListener('close', () => resolve(app.move), { once: true });
app.render({ force: true });
});

View file

@ -232,7 +232,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @protected
*/
async _prepareLoadoutContext(context, _options) {
context.cardView = !game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList);
context.cardView = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsCard);
}
/**
@ -722,8 +722,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @type {ApplicationClickAction}
*/
static async #toggleLoadoutView(_, button) {
const newAbilityView = button.dataset.value !== 'true';
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsList, newAbilityView);
const newAbilityView = button.dataset.value === 'true';
await game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.displayDomainCardsAsCard, newAbilityView);
this.render();
}

View file

@ -455,7 +455,7 @@ export default function DHApplicationMixin(Base) {
options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.id)
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
});
if (deletable)

View file

@ -8,7 +8,7 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
tagifyConfigs: [
{
selector: '.features-input',
options: () => CONFIG.DH.ITEM.armorFeatures,
options: () => CONFIG.DH.ITEM.orderedArmorFeatures(),
callback: ArmorSheet.#onFeatureSelect
}
]

View file

@ -8,7 +8,7 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
tagifyConfigs: [
{
selector: '.features-input',
options: () => CONFIG.DH.ITEM.weaponFeatures,
options: () => CONFIG.DH.ITEM.orderedWeaponFeatures(),
callback: WeaponSheet.#onFeatureSelect
}
]

View file

@ -0,0 +1,2 @@
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
export { default as DhSidebar } from './sidebar.mjs';

View file

@ -0,0 +1,33 @@
export default class DhSidebar extends Sidebar {
/** @override */
static TABS = {
...super.TABS,
daggerheartMenu: {
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
img: 'systems/daggerheart/assets/logos/FoundryBorneLogoWhite.svg'
}
};
/** @override */
static PARTS = {
tabs: {
id: 'tabs',
template: 'systems/daggerheart/templates/sidebar/tabs.hbs'
}
};
/** @override */
async _prepareTabContext(context, options) {
context.tabs = Object.entries(this.constructor.TABS).reduce((obj, [k, v]) => {
let { documentName, gmOnly, tooltip, icon, img } = v;
if (gmOnly && !game.user.isGM) return obj;
if (documentName) {
tooltip ??= getDocumentClass(documentName).metadata.labelPlural;
icon ??= CONFIG[documentName]?.sidebarIcon;
}
obj[k] = { tooltip, icon, img };
obj[k].active = this.tabGroups.primary === k;
return obj;
}, {});
}
}

View file

@ -0,0 +1,160 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { AbstractSidebarTab } = foundry.applications.sidebar;
/**
* The daggerheart menu tab.
* @extends {AbstractSidebarTab}
* @mixes HandlebarsApplication
*/
export default class DaggerheartMenu extends HandlebarsApplicationMixin(AbstractSidebarTab) {
constructor(options) {
super(options);
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
}
static #defaultRefreshSelections() {
return {
session: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.session') },
scene: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.scene') },
longRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.longrest') },
shortRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.shortrest') }
};
}
/** @override */
static DEFAULT_OPTIONS = {
classes: ['dh-style'],
window: {
title: 'SIDEBAR.TabSettings'
},
actions: {
selectRefreshable: DaggerheartMenu.#selectRefreshable,
refreshActors: DaggerheartMenu.#refreshActors
}
};
/** @override */
static tabName = 'daggerheartMenu';
/** @override */
static PARTS = {
main: { template: 'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs' }
};
/* -------------------------------------------- */
/** @inheritDoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.refreshables = this.refreshSelections;
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
return context;
}
async getRefreshables(types) {
const refreshedActors = {};
for (let actor of game.actors) {
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
const updates = {};
for (let item of actor.items) {
if (item.system.metadata.hasResource && types.includes(item.system.resource?.recovery)) {
if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add(
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
);
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
const increasing =
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
updates[item.id].system = {
...updates[item.id].system,
'resource.value': increasing
? 0
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
};
}
if (item.system.metadata.hasActions) {
const refreshTypes = new Set();
const actions = item.system.actions.filter(action => {
if (types.includes(action.uses.recovery)) {
refreshTypes.add(action.uses.recovery);
return true;
}
return false;
});
if (actions.length === 0) continue;
if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add(
...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
);
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
updates[item.id].system = {
...updates[item.id].system,
...actions.reduce(
(acc, action) => {
acc.actions[action.id] = { 'uses.value': 0 };
return acc;
},
{ actions: updates[item.id].system.actions ?? {} }
)
};
}
}
for (let key in updates) {
const update = updates[key];
await actor.items.get(key).update(update);
}
}
}
return refreshedActors;
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
static async #selectRefreshable(_event, button) {
const { type } = button.dataset;
this.refreshSelections[type].selected = !this.refreshSelections[type].selected;
this.render();
}
static async #refreshActors() {
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
await this.getRefreshables(refreshKeys);
const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', ');
ui.notifications.info(
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
types: `[${types}]`
})
);
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
const cls = getDocumentClass('ChatMessage');
const msg = {
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
{
types: types
}
),
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
speaker: cls.getSpeaker()
};
cls.create(msg);
this.render();
}
}

View file

@ -1,4 +1,4 @@
export const displayDomainCardsAsList = 'displayDomainCardsAsList';
export const displayDomainCardsAsCard = 'displayDomainCardsAsCard';
export const narrativeCountdown = {
simple: 'countdown-narrative-simple',
position: 'countdown-narrative-position'

View file

@ -90,22 +90,22 @@ export const rangeInclusion = {
export const otherTargetTypes = {
friendly: {
id: 'friendly',
label: 'Friendly'
label: 'DAGGERHEART.CONFIG.TargetTypes.friendly'
},
hostile: {
id: 'hostile',
label: 'Hostile'
label: 'DAGGERHEART.CONFIG.TargetTypes.hostile'
},
any: {
id: 'any',
label: 'Any'
label: 'DAGGERHEART.CONFIG.TargetTypes.any'
}
};
export const targetTypes = {
self: {
id: 'self',
label: 'Self'
label: 'DAGGERHEART.CONFIG.TargetTypes.self'
},
...otherTargetTypes
};
@ -587,17 +587,17 @@ export const abilityCosts = {
},
hope: {
id: 'hope',
label: 'Hope',
label: 'DAGGERHEART.CONFIG.HealingType.hope.name',
group: 'TYPES.Actor.character'
},
armor: {
id: 'armor',
label: 'Armor Slot',
label: 'DAGGERHEART.CONFIG.HealingType.armor.name',
group: 'TYPES.Actor.character'
},
fear: {
id: 'fear',
label: 'Fear',
label: 'DAGGERHEART.CONFIG.HealingType.fear.name',
group: 'TYPES.Actor.adversary'
},
resource: itemAbilityCosts.resource

View file

@ -370,6 +370,19 @@ export const typeConfig = {
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
}
],
filters: []
},
beastforms: {
columns: [
{
key: 'system.tier',
label: 'DAGGERHEART.GENERAL.Tiers.singular'
},
{
key: 'system.mainTrait',
label: 'DAGGERHEART.GENERAL.Trait.single'
}
],
filters: [
{
key: 'system.linkedClass.uuid',

View file

@ -452,6 +452,34 @@ export const armorFeatures = {
}
};
export const allArmorFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.armorFeatures;
return {
...armorFeatures,
...Object.keys(homebrewFeatures).reduce((acc, key) => {
const feature = homebrewFeatures[key];
acc[key] = { ...feature, label: feature.name };
return acc;
}, {})
};
};
export const orderedArmorFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.armorFeatures;
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
const all = Object.keys(allFeatures).map(key => {
const feature = allFeatures[key];
return {
...feature,
id: key,
label: feature.label ?? feature.name
};
});
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
};
export const weaponFeatures = {
barrier: {
label: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.name',
@ -1383,6 +1411,34 @@ export const weaponFeatures = {
}
};
export const allWeaponFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.weaponFeatures;
return {
...weaponFeatures,
...Object.keys(homebrewFeatures).reduce((acc, key) => {
const feature = homebrewFeatures[key];
acc[key] = { ...feature, label: feature.name };
return acc;
}, {})
};
};
export const orderedWeaponFeatures = () => {
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
.weaponFeatures;
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
const all = Object.keys(allFeatures).map(key => {
const feature = allFeatures[key];
return {
...feature,
id: key,
label: feature.label ?? feature.name
};
});
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
};
export const featureTypes = {
ancestry: {
id: 'ancestry',

View file

@ -28,7 +28,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
actionType: new fields.StringField({
choices: CONFIG.DH.ITEM.actionTypes,
initial: 'action',
nullable: true
nullable: false,
required: true
})
};

View file

@ -30,4 +30,24 @@ export default class BaseEffect extends foundry.abstract.TypeDataModel {
})
};
}
static getDefaultObject() {
return {
name: 'New Effect',
id: foundry.utils.randomID(),
disabled: false,
img: 'icons/magic/life/heart-cross-blue.webp',
description: '',
statuses: [],
changes: [],
system: {
rangeDependence: {
enabled: false,
type: CONFIG.DH.GENERAL.rangeInclusion.withinRange.id,
target: CONFIG.DH.GENERAL.otherTargetTypes.hostile.id,
range: CONFIG.DH.GENERAL.range.melee.id
}
}
};
}
}

View file

@ -218,7 +218,8 @@ export default class CostField extends fields.ArrayField {
* @returns
*/
static getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
const cloneCosts = foundry.utils.deepClone(costs),
realCosts = cloneCosts?.length ? cloneCosts.filter(c => c.enabled) : [];
let mergedCosts = [];
realCosts.forEach(c => {
const getCost = Object.values(mergedCosts).find(gc => gc.key === c.key);

View file

@ -206,7 +206,9 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.multiplierTypes,
initial: 'prof',
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier'
label: 'DAGGERHEART.ACTIONS.Config.damage.multiplier',
nullable: false,
required: true
}),
flatMultiplier: new fields.NumberField({
nullable: true,
@ -216,7 +218,9 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
dice: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: 'd6',
label: 'DAGGERHEART.GENERAL.Dice.single'
label: 'DAGGERHEART.GENERAL.Dice.single',
nullable: false,
required: true
}),
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'DAGGERHEART.GENERAL.bonus' }),
custom: new fields.SchemaField({

View file

@ -50,7 +50,7 @@ export default class EffectsField extends fields.ArrayField {
let effects = this.effects;
const messageTargets = [];
targets.forEach(async baseToken => {
if (this.hasSave && baseToken.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
if (!effects.length) return;
const token = canvas.tokens.get(baseToken.id);

View file

@ -15,13 +15,17 @@ export class DHActionRollData extends foundry.abstract.DataModel {
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
advState: new fields.StringField({
choices: CONFIG.DH.ACTIONS.advantageState,
initial: 'neutral'
initial: 'neutral',
nullable: false,
required: true
}),
diceRolling: new fields.SchemaField({
multiplier: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceSetNumbers,
initial: 'prof',
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.multiplier'
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.multiplier',
nullable: false,
required: true
}),
flatMultiplier: new fields.NumberField({
nullable: true,
@ -31,7 +35,9 @@ export class DHActionRollData extends foundry.abstract.DataModel {
dice: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: CONFIG.DH.GENERAL.diceTypes.d6,
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.dice'
label: 'DAGGERHEART.ACTIONS.RollField.diceRolling.dice',
nullable: false,
required: true
}),
compare: new fields.StringField({
choices: CONFIG.DH.ACTIONS.diceCompare,

View file

@ -19,7 +19,9 @@ export default class SaveField extends fields.SchemaField {
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
damageMod: new fields.StringField({
initial: CONFIG.DH.ACTIONS.damageOnSave.none.id,
choices: CONFIG.DH.ACTIONS.damageOnSave
choices: CONFIG.DH.ACTIONS.damageOnSave,
nullable: false,
required: true
})
};
super(saveFields, options, context);

View file

@ -7,7 +7,8 @@ export default class TargetField extends fields.SchemaField {
type: new fields.StringField({
choices: CONFIG.DH.GENERAL.targetTypes,
initial: CONFIG.DH.GENERAL.targetTypes.any.id,
nullable: true
nullable: true,
blank: true
}),
amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
};

View file

@ -1,5 +1,4 @@
import AttachableItem from './attachableItem.mjs';
import { armorFeatures } from '../../config/itemConfig.mjs';
export default class DHArmor extends AttachableItem {
/** @inheritDoc */
@ -25,7 +24,7 @@ export default class DHArmor extends AttachableItem {
new fields.SchemaField({
value: new fields.StringField({
required: true,
choices: CONFIG.DH.ITEM.armorFeatures,
choices: CONFIG.DH.ITEM.allArmorFeatures,
blank: true
}),
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
@ -60,13 +59,14 @@ export default class DHArmor extends AttachableItem {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
const changedArmorFeatures = changes.system?.armorFeatures ?? [];
const removedFeatures = this.armorFeatures.filter(x => changedArmorFeatures.every(y => y.value !== x.value));
if (changes.system?.armorFeatures) {
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));
const added = changedArmorFeatures.filter(x => this.armorFeatures.every(y => y.value !== x.value));
const effectIds = [];
const actionIds = [];
for (var feature of removed) {
for (var feature of removedFeatures) {
effectIds.push(...feature.effectIds);
actionIds.push(...feature.actionIds);
}
@ -76,8 +76,9 @@ export default class DHArmor extends AttachableItem {
return acc;
}, {});
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
for (const feature of added) {
const featureData = armorFeatures[feature.value];
const featureData = allFeatures[feature.value];
if (featureData.effects?.length > 0) {
const embeddedItems = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -91,7 +92,7 @@ export default class DHArmor extends AttachableItem {
}
const newActions = {};
if (featureData.actions?.length > 0) {
if (featureData.actions?.length > 0 || featureData.actions?.size > 0) {
for (let action of featureData.actions) {
const embeddedEffects = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -110,10 +111,12 @@ export default class DHArmor extends AttachableItem {
{
...cls.getSourceConfig(this),
...action,
type: action.type,
_id: actionId,
name: game.i18n.localize(action.name),
description: game.i18n.localize(action.description),
effects: embeddedEffects.map(x => ({ _id: x.id }))
effects: embeddedEffects.map(x => ({ _id: x.id })),
systemPath: 'actions'
},
{ parent: this }
);
@ -126,6 +129,10 @@ export default class DHArmor extends AttachableItem {
}
}
_onUpdate(a, b, c) {
super._onUpdate(a, b, c);
}
/**
* Generates a list of localized tags based on this item's type-specific properties.
* @returns {string[]} An array of localized tag strings.
@ -145,7 +152,8 @@ export default class DHArmor extends AttachableItem {
*/
_getLabels() {
const labels = [];
if(this.baseScore) labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`)
if (this.baseScore)
labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`);
return labels;
}

View file

@ -39,7 +39,7 @@ export default class DHWeapon extends AttachableItem {
new fields.SchemaField({
value: new fields.StringField({
required: true,
choices: CONFIG.DH.ITEM.weaponFeatures,
choices: CONFIG.DH.ITEM.allWeaponFeatures,
blank: true
}),
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
@ -116,13 +116,14 @@ export default class DHWeapon extends AttachableItem {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
const changedWeaponFeatures = changes.system?.weaponFeatures ?? [];
const removedFeatures = this.weaponFeatures.filter(x => changedWeaponFeatures.every(y => y.value !== x.value));
if (changes.system?.weaponFeatures) {
const removed = this.weaponFeatures.filter(x => !changes.system.weaponFeatures.includes(x));
const added = changes.system.weaponFeatures.filter(x => !this.weaponFeatures.includes(x));
const added = changedWeaponFeatures.filter(x => this.weaponFeatures.every(y => y.value !== x.value));
const removedEffectsUpdate = [];
const removedActionsUpdate = [];
for (let weaponFeature of removed) {
for (let weaponFeature of removedFeatures) {
removedEffectsUpdate.push(...weaponFeature.effectIds);
removedActionsUpdate.push(...weaponFeature.actionIds);
}
@ -133,8 +134,9 @@ export default class DHWeapon extends AttachableItem {
return acc;
}, {});
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
for (let weaponFeature of added) {
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
const featureData = allFeatures[weaponFeature.value];
if (featureData.effects?.length > 0) {
const embeddedItems = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -148,7 +150,7 @@ export default class DHWeapon extends AttachableItem {
}
const newActions = {};
if (featureData.actions?.length > 0) {
if (featureData.actions?.length > 0 || featureData.actions?.size > 0) {
for (let action of featureData.actions) {
const embeddedEffects = await this.parent.createEmbeddedDocuments(
'ActiveEffect',
@ -170,10 +172,12 @@ export default class DHWeapon extends AttachableItem {
{
...cls.getSourceConfig(this),
...action,
type: action.type,
_id: actionId,
name: game.i18n.localize(action.name),
description: game.i18n.localize(action.description),
effects: embeddedEffects.map(x => ({ _id: x.id }))
effects: embeddedEffects.map(x => ({ _id: x.id })),
systemPath: 'actions'
},
{ parent: this }
);

View file

@ -169,7 +169,7 @@ export const defaultLevelTiers = {
tiers: {
2: {
tier: 2,
name: 'Tier 2',
name: 'DAGGERHEART.APPLICATIONS.Levelup.tier2.name',
levels: {
start: 2,
end: 4
@ -232,7 +232,7 @@ export const defaultLevelTiers = {
},
3: {
tier: 3,
name: 'Tier 3',
name: 'DAGGERHEART.APPLICATIONS.Levelup.tier3.name',
levels: {
start: 5,
end: 7
@ -313,7 +313,7 @@ export const defaultLevelTiers = {
},
4: {
tier: 4,
name: 'Tier 4',
name: 'DAGGERHEART.APPLICATIONS.Levelup.tier4.name',
levels: {
start: 8,
end: 10

View file

@ -234,7 +234,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
const subclassInTier = subclasses.some(x => x.tier === Number(tierKey));
return {
name: tier.name,
name: game.i18n.localize(tier.name),
active: this.currentLevel >= Math.min(...tier.belongingLevels),
groups: Object.keys(tier.options).map(optionKey => {
const option = tier.options[optionKey];

View file

@ -115,7 +115,35 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
label: new fields.StringField({ required: true, label: 'DAGGERHEART.GENERAL.label' }),
description: new fields.StringField()
})
)
),
itemFeatures: new fields.SchemaField({
weaponFeatures: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new ActionsField(),
effects: new fields.ArrayField(new fields.ObjectField())
})
),
armorFeatures: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
base64: false
}),
description: new fields.HTMLField(),
actions: new ActionsField(),
effects: new fields.ArrayField(new fields.ObjectField())
})
)
})
};
}
}

View file

@ -32,7 +32,7 @@ export default class DHItem extends foundry.documents.Item {
/** @inheritDoc */
static migrateData(source) {
if(source.system?.attack && !source.system.attack.type) source.system.attack.type = "attack";
if (source.system?.attack && !source.system.attack.type) source.system.attack.type = 'attack';
return super.migrateData(source);
}

View file

@ -9,12 +9,12 @@ export default function DhDualityRollEnricher(match, _options) {
}
function getDualityMessage(roll, flavor) {
const trait = roll.trait && abilities[roll.trait] ? game.i18n.localize(abilities[roll.trait].label) : null;
const trait = roll?.trait && abilities[roll.trait] ? game.i18n.localize(abilities[roll.trait].label) : null;
const label =
flavor ??
(roll.trait
(roll?.trait
? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait })
: roll.reaction
: roll?.reaction
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
: game.i18n.localize('DAGGERHEART.GENERAL.duality'));
@ -22,9 +22,9 @@ function getDualityMessage(roll, flavor) {
? game.i18n.localize(abilities[roll.trait].label)
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
const advantage = roll.advantage
const advantage = roll?.advantage
? CONFIG.DH.ACTIONS.advantageState.advantage.value
: roll.disadvantage
: roll?.disadvantage
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
: undefined;
const advantageLabel =
@ -36,21 +36,21 @@ function getDualityMessage(roll, flavor) {
const dualityElement = document.createElement('span');
dualityElement.innerHTML = `
<button type="button" class="duality-roll-button${roll.inline ? ' inline' : ''}"
<button type="button" class="duality-roll-button${roll?.inline ? ' inline' : ''}"
data-title="${label}"
data-label="${dataLabel}"
data-reaction="${roll.reaction ? 'true' : 'false'}"
data-hope="${roll.hope ?? 'd12'}"
data-fear="${roll.fear ?? 'd12'}"
data-reaction="${roll?.reaction ? 'true' : 'false'}"
data-hope="${roll?.hope ?? 'd12'}"
data-fear="${roll?.fear ?? 'd12'}"
${advantage ? `data-advantage="${advantage}"` : ''}
${roll.difficulty !== undefined ? `data-difficulty="${roll.difficulty}"` : ''}
${roll.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
${roll.advantage ? 'data-advantage="true"' : ''}
${roll.disadvantage ? 'data-disadvantage="true"' : ''}
${roll?.difficulty !== undefined ? `data-difficulty="${roll.difficulty}"` : ''}
${roll?.trait && abilities[roll.trait] ? `data-trait="${roll.trait}"` : ''}
${roll?.advantage ? 'data-advantage="true"' : ''}
${roll?.disadvantage ? 'data-disadvantage="true"' : ''}
>
${roll.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
${roll?.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
${label}
${!flavor && (roll.difficulty || advantageLabel) ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
${!flavor && (roll?.difficulty || advantageLabel) ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
</button>
`;

View file

@ -29,14 +29,11 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
'systems/daggerheart/templates/dialogs/downtime/activities.hbs',
'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs',
'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/target-part.hbs',
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
'systems/daggerheart/templates/scene/dh-config.hbs'
]);
};

View file

@ -1,10 +1,11 @@
---
name: Pull Request
about: Create a new pull request
title: "[PR] <Insert Title here>"
title: '[PR] <Insert Title here>'
labels: pr
assignees: ''
---
Is this a community PR? Please go to preview tab and click [here](?expand=1&template=community_pull_request_template.md). If not, delete this line.
## Description

View file

@ -438,14 +438,14 @@
"_id": "UpFsnlbZkyvM2Ftv",
"img": "icons/magic/acid/projectile-smoke-glowing.webp",
"system": {
"description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
"description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
"resource": null,
"actions": {
"yd10HwK6Wa3OEvv2": {
"type": "attack",
"_id": "yd10HwK6Wa3OEvv2",
"systemPath": "actions",
"description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
"description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@ -557,11 +557,11 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": "MQSznptE5yLT7kj8",
"modifiedTime": 1754143653876
"systemVersion": "1.1.2",
"lastModifiedBy": "mdk78Q6pOyHh6aBg",
"modifiedTime": 1756510879809
},
"_key": "!actors.items!89yAh30vaNQOALlz.UpFsnlbZkyvM2Ftv"
},

View file

@ -269,14 +269,14 @@
"name": "Crushing Blows",
"type": "feature",
"system": {
"description": "<p>When the Elemental makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"description": "<p>When the Elemental makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"resource": null,
"actions": {
"0sXciTiPc30v8czv": {
"type": "damage",
"_id": "0sXciTiPc30v8czv",
"systemPath": "actions",
"description": "<p>When the Elemental makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"description": "<p>When the Elemental makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@ -342,12 +342,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.1.2",
"createdTime": 1754127683751,
"modifiedTime": 1754127795809,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1756511006257,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!actors.items!dsfB3YhoL5SudvS2.NnCkXIuATO0s3tSR"
},

View file

@ -275,14 +275,14 @@
"name": "Acidic Form",
"type": "feature",
"system": {
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"resource": null,
"actions": {
"gtT2oHSyZg9OHHJD": {
"type": "damage",
"_id": "gtT2oHSyZg9OHHJD",
"systemPath": "actions",
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@ -348,12 +348,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.1.2",
"createdTime": 1754129153649,
"modifiedTime": 1754129204931,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1756510982337,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!actors.items!6hbqmxDXFOzZJDk4.BQsVuuwFYByKwesR"
},

View file

@ -110,19 +110,20 @@
"source": "Daggerheart SRD",
"page": 95,
"artist": ""
}
},
"motivesAndTactics": "Hide in plain sight, preserve the forest, root down, swing branches"
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.347",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "1.0.5",
"systemVersion": "1.1.2",
"createdTime": 1753922784314,
"modifiedTime": 1755385515496,
"lastModifiedBy": "VZIeX2YDvX338Zvr"
"modifiedTime": 1757057641714,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_id": "XK78QUfY8c8Go8Uv",
"sort": 3400000,

View file

@ -431,7 +431,7 @@
"_key": "!actors.items!3aAS2Qm3R6cgaYfE.tQgxiSS48TJ3X1Dl"
},
{
"name": "Avalance Roar",
"name": "Avalanche Roar",
"type": "feature",
"system": {
"description": "<p><strong>Spend a Fear</strong> to roar while within a cave and cause a cave-in. All targets within Close range must succeed on an Agility Reaction Roll (14) or take <strong>2d10</strong> physical damage. The rubble can be cleared with a Progress Countdown (8).</p><p>@Template[type:emanation|range:c]</p>",
@ -535,12 +535,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754085059319,
"modifiedTime": 1754143365810,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1756256613353,
"lastModifiedBy": "CEZZA7TXd7uT8O2c"
},
"_key": "!actors.items!3aAS2Qm3R6cgaYfE.9Z0i0uURfBMVIapJ"
},

View file

@ -229,14 +229,14 @@
"_id": "WpOh5kHHx7lcTvEY",
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
"system": {
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"resource": null,
"actions": {
"HfK0u0c7NRppuF1Q": {
"type": "damage",
"_id": "HfK0u0c7NRppuF1Q",
"systemPath": "actions",
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"description": "<p>When the Ooze makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they cant mark an Armor Slot, they must mark an additional HP.</p>",
"chatDisplay": true,
"actionType": "action",
"cost": [],
@ -301,12 +301,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.1.2",
"createdTime": 1754055148507,
"modifiedTime": 1754145130460,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1756510967769,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!actors.items!aLkLFuVoKz2NLoBK.WpOh5kHHx7lcTvEY"
}

View file

@ -552,7 +552,7 @@
"_key": "!actors.items!UGPiPLJsPvMTSKEF.QV2ytK4b1VWF71OS"
},
{
"name": "Avalance",
"name": "Avalanche",
"type": "feature",
"system": {
"description": "<p><strong>Spend a Fear</strong> to have the Dragon unleash a huge downfall of snow and ice, covering all other creatures within Far range. All targets within this area must succeed on an Instinct Reaction Roll or be buried in snow and rocks, becoming <em>Vulnerable</em> until they dig themselves out from the debris. For each PC that fails the reaction roll, you gain a Fear.</p><p>@Template[type:emanation|range:f]</p>",
@ -681,12 +681,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754131703390,
"modifiedTime": 1754131790034,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1756256581072,
"lastModifiedBy": "CEZZA7TXd7uT8O2c"
},
"_key": "!actors.items!UGPiPLJsPvMTSKEF.CcRTxCDCJskiu3fI"
},

View file

@ -182,7 +182,7 @@
"_key": "!actors.items!acMu9wJrMZZzLSTJ.cIAMenvMXHPTpOFn"
},
{
"name": "Avalance",
"name": "Avalanche",
"type": "feature",
"system": {
"description": "<p>Spend a Fear to carve the mountain with an icy torrent, causing an avalanche. All PCs in its path must succeed on an Agility or Strength Reaction Roll or be bowled over and carried down the mountain. A PC using rope, pitons, or other climbing gear gains advantage on this roll. Targets who fail are knocked down the mountain to Far range, take <strong>2d20</strong> physical damage, and must mark a Stress. Targets who succeed must mark a Stress. </p><section id=\"secret-3gM8fEJj1vD9W88k\" class=\"secret\"><p><em>How do the PCs try to weather the avalanche? What approach do the characters take to fi nd one another when their companions go hurtling down the mountainside?</em></p></section>",
@ -257,12 +257,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754217019442,
"modifiedTime": 1754217102897,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1756256534443,
"lastModifiedBy": "CEZZA7TXd7uT8O2c"
},
"_key": "!actors.items!acMu9wJrMZZzLSTJ.jkm03DXYYajsRk2j"
},

View file

@ -110,13 +110,14 @@
"effects": [
{
"name": "Protective",
"description": "Add your character's Tier to your Armor Score",
"description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier"
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "i5HfkF5aKQuUCTEG",
@ -125,7 +126,12 @@
"disabled": false,
"duration": {
"startTime": null,
"combat": null
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"origin": null,
"tint": "#ffffff",
@ -137,12 +143,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794875150,
"modifiedTime": 1753794875150,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1756682958806,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!items.effects!hiEOGF2reabGLUoi.i5HfkF5aKQuUCTEG"
}

View file

@ -110,13 +110,14 @@
"effects": [
{
"name": "Protective",
"description": "Add your character's Tier to your Armor Score",
"description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier"
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "cXWSV50apzaNQkdA",
@ -125,7 +126,12 @@
"disabled": false,
"duration": {
"startTime": null,
"combat": null
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"origin": null,
"tint": "#ffffff",
@ -137,12 +143,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794098464,
"modifiedTime": 1753794098464,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1756682973559,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!items.effects!DlinEBGZfIlvreO3.cXWSV50apzaNQkdA"
}

View file

@ -110,13 +110,14 @@
"effects": [
{
"name": "Protective",
"description": "Add your character's Tier to your Armor Score",
"description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier"
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "Z2p00q5h6x6seXys",
@ -125,7 +126,12 @@
"disabled": false,
"duration": {
"startTime": null,
"combat": null
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"origin": null,
"tint": "#ffffff",
@ -137,12 +143,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753796983285,
"modifiedTime": 1753796983285,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1756682777682,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!items.effects!A28WL9E2lJ3iLZHW.Z2p00q5h6x6seXys"
}

View file

@ -110,13 +110,14 @@
"effects": [
{
"name": "Protective",
"description": "Add your character's Tier to your Armor Score",
"description": "<p>Add the item's Tier to your Armor Score.</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier"
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "M70a81e0Mg66jHRL",
@ -125,7 +126,12 @@
"disabled": false,
"duration": {
"startTime": null,
"combat": null
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"origin": null,
"tint": "#ffffff",
@ -137,12 +143,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.348",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794114980,
"modifiedTime": 1753794114980,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1756682994216,
"lastModifiedBy": "mdk78Q6pOyHh6aBg"
},
"_key": "!items.effects!mxwWKDujgsRcZWPT.M70a81e0Mg66jHRL"
}

View file

@ -15,7 +15,7 @@ body.game:is(.performance-low, .noblur) {
.themed.theme-dark.application.daggerheart.sheet.dh-style,
&.theme-dark .application.daggerheart {
background: @dark-blue;
};
}
}
.application.sheet.dh-style {

View file

@ -14,4 +14,4 @@
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
}
}
}
}

View file

@ -0,0 +1,13 @@
.daggerheart.chat.refresh-message {
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
.subtitle {
font-size: 18;
font-weight: bold;
}
}
}

View file

@ -3,7 +3,7 @@
@import './chat/chat.less';
@import './chat/damage-summary.less';
@import './chat/downtime.less';
@import './chat/effect-summary.less';
@import './chat/refresh-message.less';
@import './chat/sheet.less';
@import './combat-sidebar/combat-sidebar.less';
@ -21,6 +21,8 @@
@import './resources/resources.less';
@import './settings/settings.less';
@import './settings/homebrew-settings/domains.less';
@import './settings/homebrew-settings/types.less';
@import './sidebar/tabs.less';
@import './sidebar/daggerheartMenu.less';

View file

@ -0,0 +1,38 @@
.tab.sidebar-tab.daggerheartMenu-sidebar {
padding: 0 4px;
.menu-refresh-container {
display: flex;
flex-direction: column;
gap: 8px;
.menu-refresh-inner-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
.experience-chip {
display: flex;
align-items: center;
border-radius: 5px;
width: fit-content;
gap: 5px;
cursor: pointer;
padding: 5px;
background: light-dark(@dark-blue-10, @golden-10);
color: light-dark(@dark-blue, @golden);
.label {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 17px;
}
&.selected {
background: light-dark(@dark-blue-40, @golden-40);
}
}
}
}
}

View file

@ -0,0 +1,8 @@
#interface #ui-right #sidebar {
menu li button {
img {
width: 22px;
max-width: unset;
}
}
}

View file

@ -1,6 +1,6 @@
<fieldset class="one-column" data-key="cost">
<legend>
Cost
{{localize "DAGGERHEART.GENERAL.Cost.single"}}
<a><i class="fa-solid fa-plus icon-button" data-action="addElement"></i></a>
</legend>
{{#each source as |cost index|}}
@ -8,10 +8,10 @@
{{formField ../fields.consumeOnSuccess value=cost.consumeOnSuccess name=(concat "cost." index ".consumeOnSuccess") classes="checkbox" rootId=partId localize=true}}
{{/if}}
<div class="nest-inputs">
{{formField ../fields.scalable label="Scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox"}}
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="Resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
{{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)}}
{{formField ../fields.scalable label="DAGGERHEART.GENERAL.scalable" value=cost.scalable name=(concat "cost." index ".scalable") classes="checkbox" localize=true}}
{{formField ../fields.key choices=(@root.disableOption index @root.costOptions ../source) label="DAGGERHEART.GENERAL.resource" value=cost.key name=(concat "cost." index ".key") localize=true blank=false}}
{{formField ../fields.value label="DAGGERHEART.GENERAL.amount" value=cost.value name=(concat "cost." index ".value") localize=true}}
{{formField ../fields.step label="DAGGERHEART.GENERAL.step" value=cost.step name=(concat "cost." index ".step") disabled=(not cost.scalable) localize=true}}
<a class="btn" data-tooltip="{{localize "CONTROLS.CommonDelete"}}" data-action="removeElement" data-index="{{index}}"><i class="fas fa-trash"></i></a>
</div>
{{/each}}

View file

@ -1,12 +1,12 @@
<fieldset class="one-column">
<legend>{{localize "DAGGERHEART.GENERAL.range"}}{{#if fields.target}} & {{localize "DAGGERHEART.GENERAL.Target.single"}}{{/if}}</legend>
{{formField fields.range value=source.range label="Range" name=(concat path "range") localize=true}}
{{formField fields.range value=source.range label="DAGGERHEART.GENERAL.range" name=(concat path "range") localize=true}}
{{#if fields.target}}
<div class="nest-inputs">
{{#if (and source.target.type (not (eq source.target.type 'self')))}}
{{ formField fields.target.amount value=source.target.amount label="Amount" name=(concat path "target.amount") }}
{{ formField fields.target.amount value=source.target.amount label="DAGGERHEART.GENERAL.amount" name=(concat path "target.amount") localize=true}}
{{/if}}
{{ formField fields.target.type value=source.target.type label="Target" name=(concat path "target.type") localize=true }}
{{ formField fields.target.type value=source.target.type label="DAGGERHEART.GENERAL.Target.single" name=(concat path "target.type") localize=true }}
</div>
{{/if}}
</fieldset>

View file

@ -4,8 +4,8 @@
{{formField fields.consumeOnSuccess value=source.consumeOnSuccess name="uses.consumeOnSuccess" classes="checkbox" rootId=partId localize=true}}
{{/if}}
<div class="nest-inputs">
{{formField fields.value label="Spent" value=source.value name="uses.value" rootId=partId}}
{{formField fields.max label="Max" value=source.max name="uses.max" rootId=partId}}
{{formField fields.value label="DAGGERHEART.GENERAL.spent" value=source.value name="uses.value" rootId=partId localize=true}}
{{formField fields.max label="DAGGERHEART.GENERAL.max" value=source.max name="uses.max" rootId=partId localize=true}}
</div>
{{formField fields.recovery label="Recovery" value=source.recovery name="uses.recovery" rootId=partId localize=true}}
{{formField fields.recovery label="DAGGERHEART.GENERAL.recovery" value=source.recovery name="uses.recovery" rootId=partId localize=true}}
</fieldset>

View file

@ -146,7 +146,7 @@
<select class="roll-mode-select" name="selectedRollMode">
{{selectOptions rollModes selected=selectedRollMode valueAttr="action" labelAttr="label" localize=true}}
</select>
<button class="sunmit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>
<button class="submit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>
<i class="fa-solid fa-dice"></i>
<span class="label">
{{#if @root.rollConfig.roll.difficulty}}
@ -162,7 +162,7 @@
{{> 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs'}}
{{/if}}
<div class="roll-dialog-controls">
<button class="sunmit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>
<button class="submit-btn" data-action="submitRoll"{{#unless canRoll}} disabled{{/unless}}>
<span class="label">{{localize "DAGGERHEART.GENERAL.continue"}}</span>
</button>
</div>

View file

@ -0,0 +1,15 @@
<section
class='tab {{tabs.effects.cssClass}} {{tabs.effects.id}}'
data-tab='{{tabs.effects.id}}'
data-group='{{tabs.effects.group}}'
>
<fieldset class="one-column">
<legend>{{localize "DAGGERHEART.GENERAL.Effect.plural"}} <a><i class="fa-solid fa-plus icon-button" data-action="addEffect"></i></a></legend>
<div class="settings-items">
{{#each move.effects}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" id=this.id type="effect" }}
{{/each}}
</div>
</fieldset>
</section>

View file

@ -3,11 +3,13 @@
data-tab='{{tabs.main.id}}'
data-group='{{tabs.main.group}}'
>
<fieldset class="one-column">
<legend>{{localize "Icon"}}</legend>
{{#if hasIcon}}
<fieldset class="one-column">
<legend>{{localize "Icon"}}</legend>
<input type="text" name="icon" value="{{move.icon}}" />
</fieldset>
<input type="text" name="icon" value="{{move.icon}}" />
</fieldset>
{{/if}}
<fieldset class="one-column">
<legend>{{localize "Description"}}</legend>

View file

@ -0,0 +1,35 @@
<section
class="tab {{tabs.itemFeatures.cssClass}} {{tabs.itemFeatures.id}}"
data-tab="{{tabs.itemFeatures.id}}"
data-group="{{tabs.itemFeatures.group}}"
>
<div class="two-columns even">
<fieldset class="start-align">
<legend>
{{localize "DAGGERHEART.GENERAL.weaponFeatures"}}
<a data-action="addItem" data-type="weaponFeatures"><i class="fa-solid fa-plus"></i></a>
<a data-action="resetMoves" data-type="weaponFeatures"><i class="fa-solid fa-arrow-rotate-left"></i></a>
</legend>
<div class="settings-items">
{{#each settingFields._source.itemFeatures.weaponFeatures as |feature id|}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" this type="weaponFeatures" id=id }}
{{/each}}
</div>
</fieldset>
<fieldset class="start-align">
<legend>
{{localize "DAGGERHEART.GENERAL.armorFeatures"}}
<a data-action="addItem" data-type="armorFeatures"><i class="fa-solid fa-plus"></i></a>
<a data-action="resetMoves" data-type="armorFeatures"><i class="fa-solid fa-arrow-rotate-left"></i></a>
</legend>
<div class="settings-items">
{{#each settingFields._source.itemFeatures.armorFeatures as |feature id|}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" this type="armorFeatures" id=id }}
{{/each}}
</div>
</fieldset>
</div>
</section>

View file

@ -8,24 +8,24 @@
</header>
<ol class="scrollable" data-changes>
{{#each source.changes as |change i|}}
{{#with ../fields.changes.element.fields as |changeFields|}}
<li data-index="{{i}}">
<div class="key">
<input type="text" class="effect-change-input" name="{{concat "changes." i ".key"}}" value="{{change.key}}" />
</div>
<div class="mode">
{{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}}
</div>
<div class="value">
{{formInput changeFields.value name=(concat "changes." i ".value") value=change.value}}
</div>
<div class="priority">
{{formInput changeFields.priority name=(concat "changes." i ".priority") value=change.priority
placeholder=(lookup ../../priorities change.mode)}}
</div>
<div class="controls"><a data-action="deleteChange"><i class="fa-solid fa-trash"></i></a></div>
</li>
{{/with}}
{{#with ../fields.changes.element.fields as |changeFields|}}
<li data-index="{{i}}">
<div class="key">
<input type="text" class="effect-change-input" name="{{concat "changes." i ".key"}}" value="{{change.key}}" />
</div>
<div class="mode">
{{formInput changeFields.mode name=(concat "changes." i ".mode") value=change.mode choices=@root.modes}}
</div>
<div class="value">
{{formInput changeFields.value name=(concat "changes." i ".value") value=change.value}}
</div>
<div class="priority">
{{formInput changeFields.priority name=(concat "changes." i ".priority") value=change.priority
placeholder=(lookup ../../priorities change.mode)}}
</div>
<div class="controls"><a data-action="deleteChange"><i class="fa-solid fa-trash"></i></a></div>
</li>
{{/with}}
{{/each}}
</ol>
</section>

View file

@ -2,10 +2,10 @@
<fieldset class="one-column">
<legend>{{localize "DAGGERHEART.ACTIVEEFFECT.Config.rangeDependence.title"}}</legend>
{{formGroup document.system.schema.fields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }}
{{formGroup document.system.schema.fields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }}
{{formGroup document.system.schema.fields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }}
{{formGroup document.system.schema.fields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }}
{{formGroup systemFields.rangeDependence.fields.enabled value=source.system.rangeDependence.enabled localize=true }}
{{formGroup systemFields.rangeDependence.fields.type value=source.system.rangeDependence.type localize=true }}
{{formGroup systemFields.rangeDependence.fields.target value=source.system.rangeDependence.target localize=true }}
{{formGroup systemFields.rangeDependence.fields.range value=source.system.rangeDependence.range localize=true }}
</fieldset>
<fieldset class="one-column">

View file

@ -0,0 +1,22 @@
<div>
<fieldset>
<legend>{{localize "Refresh Features"}}</legend>
<div class="menu-refresh-container">
<div class="menu-refresh-inner-container">
{{#each refreshables as |type key|}}
<div class="experience-chip {{#if type.selected}}selected{{/if}}" data-action="selectRefreshable" data-type="{{key}}">
{{#if type.selected}}
<span><i class="fa-solid fa-circle"></i></span>
{{else}}
<span><i class="fa-regular fa-circle"></i></span>
{{/if}}
<span>{{type.label}}</span>
</div>
{{/each}}
</div>
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "Refresh"}}</button>
</div>
</fieldset>
</div>

View file

@ -0,0 +1,18 @@
<nav class="tabs faded-ui" role="tablist" data-tooltip-direction="LEFT">
<menu class="flexcol">
{{#each tabs}}
<li>
<button type="button" class="ui-control plain icon {{#if icon}}{{icon}}{{/if}}" data-action="tab" data-tab="{{ @key }}"
role="tab" aria-pressed="{{ active }}" data-group="primary" aria-label="{{ localize tooltip }}"
aria-controls="{{ @key }}" data-tooltip>
{{#if img}}<img src="{{img}}" />{{/if}}
</button>
<div class="notification-pip"></div>
</li>
{{/each}}
<li>
<button type="button" class="collapse ui-control plain icon fas fa-caret-left" data-tooltip
aria-label="{{ localize "Expand" }}" data-action="toggleState"></button>
</li>
</menu>
</nav>

View file

@ -0,0 +1,6 @@
<div class="daggerheart chat refresh-message">
<header>
<div class="subtitle">{{localize "DAGGERHEART.UI.Chat.refreshMessage.header"}}</div>
<div class="types">{{types}}</div>
</header>
</div>