mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-22 23:43:37 +02:00
Merged with v14-Dev
This commit is contained in:
commit
4332a1ba7a
613 changed files with 4618 additions and 2996 deletions
|
|
@ -67,7 +67,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
|
|||
break;
|
||||
case 'summary':
|
||||
const levelKeys = Object.keys(this.levelup.levels);
|
||||
const actorDamageDice = this.actor.system.attack.damage.parts[0].value.dice;
|
||||
const actorDamageDice = this.actor.system.attack.damage.parts.hitPoints.value.dice;
|
||||
const actorRange = this.actor.system.attack.range;
|
||||
|
||||
let achievementExperiences = [];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export { default as DhAppearanceSettings } from './appearanceSettings.mjs';
|
||||
export { default as DhAutomationSettings } from './automationSettings.mjs';
|
||||
export { default as DhHomebrewSettings } from './homebrewSettings.mjs';
|
||||
export { default as DhMetagamingSettings } from './metagamingSettings.mjs';
|
||||
export { default as DhVariantRuleSettings } from './variantRuleSettings.mjs';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-appearance-settings',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting', 'appearance-settings'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
title: 'DAGGERHEART.SETTINGS.Menu.title',
|
||||
|
|
@ -70,6 +70,14 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement
|
||||
.querySelector('.default-animations-input')
|
||||
?.addEventListener('change', this.toggleSFXOverride.bind(this));
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_configureRenderParts(options) {
|
||||
const parts = super._configureRenderParts(options);
|
||||
|
|
@ -83,15 +91,20 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
/**@inheritdoc */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (options.isFirstRender)
|
||||
if (options.isFirstRender) {
|
||||
this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
this.globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
|
||||
}
|
||||
|
||||
context.setting = this.setting;
|
||||
context.globalOverrides = this.globalOverrides;
|
||||
context.fields = this.setting.schema.fields;
|
||||
|
||||
context.tabs = this._prepareTabs('general');
|
||||
context.dsnTabs = this._prepareTabs('diceSoNice');
|
||||
|
||||
context.isGM = game.user.isGM;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -120,6 +133,9 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
* @protected
|
||||
*/
|
||||
async prepareDiceSoNiceContext(context) {
|
||||
context.animationEvents = CONFIG.DH.GENERAL.daggerheartDiceAnimationEvents;
|
||||
context.previewAnimation = this.previewAnimation;
|
||||
|
||||
context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce(
|
||||
(acc, [k, v]) => ({
|
||||
...acc,
|
||||
|
|
@ -146,6 +162,13 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
);
|
||||
context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList();
|
||||
|
||||
const getAnimationsOptions = key => {
|
||||
const fields = context.fields.diceSoNice.fields[key].fields.sfx.fields;
|
||||
return {
|
||||
higher: fields.higher.fields.class.choices
|
||||
};
|
||||
};
|
||||
|
||||
foundry.utils.mergeObject(
|
||||
context.dsnTabs,
|
||||
['hope', 'fear', 'advantage', 'disadvantage'].reduce(
|
||||
|
|
@ -153,7 +176,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
...acc,
|
||||
[key]: {
|
||||
values: this.setting.diceSoNice[key],
|
||||
fields: this.setting.schema.getField(`diceSoNice.${key}`).fields
|
||||
fields: this.setting.schema.getField(`diceSoNice.${key}`).fields,
|
||||
animations: ['hope', 'fear'].includes(key) ? getAnimationsOptions(key) : {}
|
||||
}
|
||||
}),
|
||||
{}
|
||||
|
|
@ -169,13 +193,20 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
* @param {foundry.applications.ux.FormDataExtended} formData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async #onSubmit(event, form, formData) {
|
||||
static async #onSubmit(_event, _form, formData) {
|
||||
const data = this.setting.schema.clean(foundry.utils.expandObject(formData.object));
|
||||
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, data);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async toggleSFXOverride(event) {
|
||||
await this.globalOverrides.diceSoNiceSFXUpdate(this.setting, event.target.checked);
|
||||
this.globalOverrides = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.GlobalOverrides);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the configuration form.
|
||||
* @this {DHAppearanceSettings}
|
||||
|
|
@ -183,13 +214,25 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
*/
|
||||
static async #onPreview(_, target) {
|
||||
const formData = new foundry.applications.ux.FormDataExtended(target.closest('form'));
|
||||
const { diceSoNice } = foundry.utils.expandObject(formData.object);
|
||||
const { diceSoNice, ...rest } = foundry.utils.expandObject(formData.object);
|
||||
const { key } = target.dataset;
|
||||
const faces = ['advantage', 'disadvantage'].includes(key) ? 'd6' : 'd12';
|
||||
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||
const diceSoNiceRoll = await new foundry.dice.Roll(`1${faces}`).evaluate();
|
||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||
|
||||
const previewAnimation = rest[`${key}PreviewAnimation`];
|
||||
const events = CONFIG.DH.GENERAL.daggerheartDiceAnimationEvents;
|
||||
if (previewAnimation) {
|
||||
if (previewAnimation === events.critical.id && diceSoNice.sfx.critical.class) {
|
||||
diceSoNiceRoll.dice[0].options.sfx = { specialEffect: diceSoNice.sfx.critical.class };
|
||||
}
|
||||
if (previewAnimation === events.higher.id && diceSoNice[key].sfx.higher) {
|
||||
diceSoNiceRoll.dice[0].options.sfx = { specialEffect: diceSoNice[key].sfx.higher.class };
|
||||
}
|
||||
}
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
|||
};
|
||||
|
||||
static PARTS = {
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/deathMoves.hbs' },
|
||||
roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' },
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
||||
import { Resource } from '../../data/settings/Homebrew.mjs';
|
||||
import { slugify } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -44,6 +45,9 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
addAdversaryType: this.addAdversaryType,
|
||||
deleteAdversaryType: this.deleteAdversaryType,
|
||||
selectAdversaryType: this.selectAdversaryType,
|
||||
addResource: this.addResource,
|
||||
removeResource: this.removeResource,
|
||||
resetResourceImage: this.resetResourceImage,
|
||||
save: this.save,
|
||||
resetTokenSizes: this.resetTokenSizes,
|
||||
reset: this.reset
|
||||
|
|
@ -56,6 +60,10 @@ 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' },
|
||||
resources: {
|
||||
template: 'systems/daggerheart/templates/settings/homebrew-settings/resources.hbs',
|
||||
scrollable: ['.resource-types-container']
|
||||
},
|
||||
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' }
|
||||
|
|
@ -64,7 +72,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
main: {
|
||||
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
|
||||
tabs: [
|
||||
{ id: 'settings' },
|
||||
{ id: 'domains' },
|
||||
{ id: 'types' },
|
||||
{ id: 'resources' },
|
||||
{ id: 'itemFeatures' },
|
||||
{ id: 'downtime' }
|
||||
],
|
||||
initial: 'settings',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -77,9 +92,17 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
this.render();
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
for (const element of htmlElement.querySelectorAll('.path-field input'))
|
||||
element.addEventListener('change', this.toggleResourceIsIcon.bind(this));
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.settingFields = this.settings;
|
||||
context.schemaFields = context.settingFields.schema.fields;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
@ -103,6 +126,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
|
||||
: null;
|
||||
break;
|
||||
case 'resources':
|
||||
break;
|
||||
case 'downtime':
|
||||
context.restOptions = {
|
||||
shortRest: CONFIG.DH.GENERAL.defaultRestOptions.shortRest(),
|
||||
|
|
@ -124,6 +149,33 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
this.render();
|
||||
}
|
||||
|
||||
async toggleResourceIsIcon(event) {
|
||||
const element = event.target.closest('.resource-icon-container');
|
||||
const { actorType, resourceKey, imageKey } = element.dataset;
|
||||
|
||||
const current = this.settings.resources[actorType].resources[resourceKey].images[imageKey];
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${resourceKey}.images.${imageKey}`]: {
|
||||
isIcon: !current.isIcon,
|
||||
value: ''
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async resetResourceImage(_event, button) {
|
||||
const element = button.closest('.resource-icon-container');
|
||||
const { actorType, resourceKey, imageKey } = element.dataset;
|
||||
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${resourceKey}.images.${imageKey}`]:
|
||||
Resource.getDefaultImageData(imageKey)
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async changeCurrencyIcon(_, target) {
|
||||
const type = target.dataset.currency;
|
||||
const currentIcon = this.settings.currency[type].icon;
|
||||
|
|
@ -466,6 +518,58 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async addResource(_, target) {
|
||||
const { actorType } = target.dataset;
|
||||
const content = new foundry.data.fields.StringField({
|
||||
label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.resources.resourceIdentifier'),
|
||||
required: true
|
||||
}).toFormGroup({}, { name: 'identifier', localize: true }).outerHTML;
|
||||
|
||||
async function callback(_, button) {
|
||||
const identifier = button.form.elements.identifier.value;
|
||||
if (!identifier) return;
|
||||
|
||||
const sluggedIdentifier = slugify(identifier);
|
||||
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
await foundry.applications.api.DialogV2.prompt({
|
||||
content: content,
|
||||
rejectClose: false,
|
||||
modal: true,
|
||||
ok: { callback: callback.bind(this) },
|
||||
window: {
|
||||
title: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.resources.setResourceIdentifier')
|
||||
},
|
||||
position: { width: 400 }
|
||||
});
|
||||
}
|
||||
|
||||
static async removeResource(_, target) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize(`DAGGERHEART.SETTINGS.Homebrew.deleteResourceTitle`)
|
||||
},
|
||||
content: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.deleteResourceText')
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
const { actorType, resourceKey } = target.dataset;
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${resourceKey}`]: _del
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async save() {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.close();
|
||||
|
|
|
|||
62
module/applications/settings/metagamingSettings.mjs
Normal file
62
module/applications/settings/metagamingSettings.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { DhMetagaming } from '../../data/settings/_module.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class DhMetagamingSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor() {
|
||||
super({});
|
||||
|
||||
this.settings = new DhMetagaming(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming).toObject()
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-metagaming-settings',
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-eye-low-vision'
|
||||
},
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
save: this.save
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/settings/metagaming-settings/header.hbs' },
|
||||
general: { template: 'systems/daggerheart/templates/settings/metagaming-settings/general.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/settings/metagaming-settings/footer.hbs' }
|
||||
};
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.settingFields = this.settings;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(_event, _element, formData) {
|
||||
const updatedSettings = foundry.utils.expandObject(formData.object);
|
||||
|
||||
await this.settings.updateSource(updatedSettings);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async reset() {
|
||||
this.settings = new DhMetagaming();
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async save() {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming, this.settings.toObject());
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||
|
||||
const { ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -28,6 +29,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
removeEffect: this.removeEffect,
|
||||
addElement: this.addElement,
|
||||
removeElement: this.removeElement,
|
||||
removeTransformActor: this.removeTransformActor,
|
||||
editEffect: this.editEffect,
|
||||
addDamage: this.addDamage,
|
||||
removeDamage: this.removeDamage,
|
||||
|
|
@ -41,7 +43,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
submitOnChange: true,
|
||||
closeOnSubmit: false
|
||||
},
|
||||
dragDrop: [{ dragSelector: null, dropSelector: '#summon-drop-zone', handlers: ['_onDrop'] }]
|
||||
dragDrop: [{ dragSelector: null, dropSelector: '[data-is-drop-zone]', handlers: ['_onDrop'] }]
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
@ -103,7 +105,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
}
|
||||
};
|
||||
|
||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects', 'summon'];
|
||||
static CLEAN_ARRAYS = ['cost', 'effects', 'summon'];
|
||||
|
||||
_getTabs(tabs) {
|
||||
for (const v of Object.values(tabs)) {
|
||||
|
|
@ -120,6 +122,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
htmlElement.querySelectorAll('.summon-count-wrapper input').forEach(element => {
|
||||
element.addEventListener('change', this.updateSummonCount.bind(this));
|
||||
});
|
||||
|
||||
htmlElement.querySelectorAll('.transform-resource input').forEach(element => {
|
||||
element.addEventListener('change', this.updateTransformResource.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
|
|
@ -133,6 +139,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
context.summons.push({ actor, count: summon.count });
|
||||
}
|
||||
|
||||
if (context.source.transform) {
|
||||
const actor = await foundry.utils.fromUuid(context.source.transform.actorUUID);
|
||||
context.transform = {
|
||||
...context.source.transform,
|
||||
actor:
|
||||
actor ??
|
||||
(context.source.transform.actorUUID && !actor
|
||||
? { error: game.i18n.localize('DAGGERHEART.ACTIONS.Settings.transform.actorIsMissing') }
|
||||
: null)
|
||||
};
|
||||
}
|
||||
|
||||
context.openSection = this.openSection;
|
||||
context.tabs = this._getTabs(this.constructor.TABS);
|
||||
context.config = CONFIG.DH;
|
||||
|
|
@ -155,6 +173,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
revealed: this.openTrigger === index
|
||||
};
|
||||
});
|
||||
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
||||
|
||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||
context.tierOptions = [
|
||||
|
|
@ -266,20 +285,69 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
if (doc) return doc.sheet.render({ force: true });
|
||||
}
|
||||
|
||||
static async removeTransformActor() {
|
||||
const data = this.action.toObject();
|
||||
data.transform.actorUUID = null;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
static addDamage(_event) {
|
||||
if (!this.action.damage.parts) return;
|
||||
const data = this.action.toObject(),
|
||||
part = {};
|
||||
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
||||
data.damage.parts.push(part);
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
|
||||
const choices = getUnusedDamageTypes(this.action.damage.parts);
|
||||
const content = new foundry.data.fields.StringField({
|
||||
label: game.i18n.localize('Damage Type'),
|
||||
choices,
|
||||
required: true
|
||||
}).toFormGroup(
|
||||
{},
|
||||
{
|
||||
name: 'type',
|
||||
localize: true,
|
||||
nameAttr: 'value',
|
||||
labelAttr: 'label'
|
||||
}
|
||||
).outerHTML;
|
||||
|
||||
const callback = (_, button) => {
|
||||
const data = this.action.toObject();
|
||||
const type = choices[button.form.elements.type.value].value;
|
||||
const part = { applyTo: type };
|
||||
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
||||
data.damage.parts[type] = part;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
};
|
||||
|
||||
const typeDialog = new foundry.applications.api.DialogV2({
|
||||
buttons: [
|
||||
foundry.utils.mergeObject(
|
||||
{
|
||||
action: 'ok',
|
||||
label: 'Confirm',
|
||||
icon: 'fas fa-check',
|
||||
default: true
|
||||
},
|
||||
{ callback: callback }
|
||||
)
|
||||
],
|
||||
content: content,
|
||||
rejectClose: false,
|
||||
modal: false,
|
||||
window: {
|
||||
title: game.i18n.localize('Add Damage')
|
||||
},
|
||||
position: { width: 300 }
|
||||
});
|
||||
|
||||
typeDialog.render(true);
|
||||
}
|
||||
|
||||
static removeDamage(_event, button) {
|
||||
if (!this.action.damage.parts) return;
|
||||
const data = this.action.toObject(),
|
||||
index = button.dataset.index;
|
||||
data.damage.parts.splice(index, 1);
|
||||
const data = this.action.toObject();
|
||||
const key = button.dataset.key;
|
||||
delete data.damage.parts[key];
|
||||
data.damage.parts[`${key}`] = _del;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
|
|
@ -346,6 +414,14 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
updateTransformResource(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const data = this.action.toObject();
|
||||
data.transform.resourceRefresh[event.target.dataset.resource] = event.target.checked;
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
/** Specific implementation in extending classes **/
|
||||
static async addEffect(_event) {}
|
||||
static removeEffect(_event, _button) {}
|
||||
|
|
@ -364,6 +440,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
return;
|
||||
}
|
||||
|
||||
const dropZone = event.target.closest('[data-is-drop-zone]');
|
||||
if (!dropZone) return;
|
||||
|
||||
switch (dropZone.id) {
|
||||
case 'summon-drop-zone':
|
||||
return this.onSummonDrop(data);
|
||||
case 'transform-drop-zone':
|
||||
return this.onTransformDrop(data);
|
||||
}
|
||||
}
|
||||
|
||||
async onSummonDrop(data) {
|
||||
const actionData = this.action.toObject();
|
||||
let countvalue = 1;
|
||||
for (const entry of actionData.summon) {
|
||||
|
|
@ -380,4 +468,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
actionData.summon.push({ actorUUID: data.uuid, count: countvalue });
|
||||
await this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(actionData) });
|
||||
}
|
||||
|
||||
async onTransformDrop(data) {
|
||||
const actionData = this.action.toObject();
|
||||
actionData.transform.actorUUID = data.uuid;
|
||||
await this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(actionData) });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
||||
useDowntime: this.useDowntime,
|
||||
viewParty: CharacterSheet.#viewParty,
|
||||
toggleArmorMangement: CharacterSheet.#toggleArmorManagement
|
||||
|
|
@ -227,6 +228,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'loadout':
|
||||
await this._prepareLoadoutContext(context, options);
|
||||
break;
|
||||
|
|
@ -241,6 +245,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
return context;
|
||||
}
|
||||
|
||||
async _prepareHeaderContext(context, _options) {
|
||||
context.hasExtraResources = Object.keys(CONFIG.DH.RESOURCE.character.all).some(
|
||||
key => !CONFIG.DH.RESOURCE.character.base[key]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Loadout part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
|
|
@ -1057,6 +1067,77 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
await effect.update({ 'system.changes': newChanges });
|
||||
}
|
||||
|
||||
static async #toggleResourceManagement(event, button) {
|
||||
event.stopPropagation();
|
||||
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
|
||||
if (existingTooltip) {
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
return;
|
||||
}
|
||||
|
||||
const extraResources = Object.values(CONFIG.DH.RESOURCE.character.all).reduce((acc, resource) => {
|
||||
if (CONFIG.DH.RESOURCE.character.base[resource.id]) return acc;
|
||||
|
||||
const resourceData = this.document.system.resources[resource.id];
|
||||
acc[resource.id] = {
|
||||
id: resource.id,
|
||||
label: game.i18n.localize(resource.label),
|
||||
value: resourceData.value,
|
||||
max: resourceData.max,
|
||||
fullIcon: resource.images?.full ?? { value: 'fa-solid fa-circle', isIcon: true },
|
||||
emptyIcon: resource.images?.empty ?? { value: 'fa-regular fa-circle', isIcon: true }
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const html = document.createElement('div');
|
||||
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
|
||||
`systems/daggerheart/templates/ui/tooltip/resourceManagement.hbs`,
|
||||
{
|
||||
resources: extraResources
|
||||
}
|
||||
);
|
||||
|
||||
const target = button.closest('.resource-section');
|
||||
game.tooltip.dismissLockedTooltips();
|
||||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip',
|
||||
direction: 'DOWN',
|
||||
noOffset: true
|
||||
});
|
||||
|
||||
const resourceManager = target.querySelector('.resource-manager');
|
||||
resourceManager.classList.toggle('inverted');
|
||||
|
||||
Hooks.once(CONFIG.DH.HOOKS.hooksConfig.lockedTooltipDismissed, () => {
|
||||
resourceManager.classList.toggle('inverted');
|
||||
});
|
||||
|
||||
for (const element of html.querySelectorAll('.resource-value'))
|
||||
element.addEventListener('click', this.onUpdateResource.bind(this));
|
||||
}
|
||||
|
||||
async onUpdateResource(event) {
|
||||
const target = event.target.closest('.resource-value');
|
||||
const { resource, value: textValue } = target.dataset;
|
||||
|
||||
const inputValue = Number.parseInt(textValue);
|
||||
const decreasing = inputValue <= this.document.system.resources[resource].value;
|
||||
const value = decreasing ? inputValue - 1 : inputValue;
|
||||
await this.document.update({ [`system.resources.${resource}.value`]: value }, { render: false });
|
||||
|
||||
/* Update resource symbols */
|
||||
const section = target.closest('.resource-section');
|
||||
for (const element of section.querySelectorAll('.resource-value')) {
|
||||
const showFull = Number.parseInt(element.dataset.value) <= value;
|
||||
element.querySelector('.full').classList.toggle('hidden', !showFull);
|
||||
element.querySelector('.empty').classList.toggle('hidden', showFull);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the downtime application.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,32 @@ export default class DHBaseActorSettings extends DHApplicationMixin(DocumentShee
|
|||
const context = await super._prepareContext(options);
|
||||
context.isNPC = this.actor.isNPC;
|
||||
|
||||
if (context.systemFields.attack)
|
||||
if (context.systemFields.attack) {
|
||||
context.systemFields.attack.fields = this.actor.system.attack.schema.fields;
|
||||
}
|
||||
|
||||
// Create fake fields for actor configurable max resource value.
|
||||
const resourceConfig = CONFIG.DH.RESOURCE[this.actor.type]?.all;
|
||||
if (resourceConfig) {
|
||||
const relevant = ['hitPoints', 'stress'].filter(r => r in resourceConfig);
|
||||
context.resources = relevant.map(key => {
|
||||
const data = this.actor._source.system.resources[key];
|
||||
const config = resourceConfig[key];
|
||||
return {
|
||||
label: config.label,
|
||||
name: `system.resources.${key}.max`,
|
||||
value: data.max ?? config.max,
|
||||
tooltip: key === 'hitPoints' ? game.i18n.localize('DAGGERHEART.UI.Tooltip.maxHPClassBound') : null,
|
||||
field: new foundry.data.fields.NumberField({
|
||||
initial: config.max,
|
||||
integer: true,
|
||||
label: game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', {
|
||||
thing: game.i18n.localize(config.label)
|
||||
})
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,15 @@ export default function DHApplicationMixin(Base) {
|
|||
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
|
||||
#nonHeaderAttribution = ['environment', 'ancestry', 'community', 'domainCard'];
|
||||
|
||||
/**
|
||||
* @param {DHSheetV2Configuration} [options={}]
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this._setupDragDrop();
|
||||
}
|
||||
|
||||
/**
|
||||
* The default options for the sheet.
|
||||
* @type {DHSheetV2Configuration}
|
||||
|
|
@ -165,7 +174,9 @@ export default function DHApplicationMixin(Base) {
|
|||
/**@inheritdoc */
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
// this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||
|
||||
/* Core dragDrop from ActorDocument is always only 1. Possible we could refactor our own */
|
||||
if (Array.isArray(this._dragDrop)) this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||
|
||||
// Handle delta inputs
|
||||
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||
|
|
@ -338,6 +349,26 @@ export default function DHApplicationMixin(Base) {
|
|||
/* Drag and Drop */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Creates drag-drop handlers from the configured options.
|
||||
* @returns {foundry.applications.ux.DragDrop[]}
|
||||
* @private
|
||||
*/
|
||||
_setupDragDrop() {
|
||||
if (this._dragDrop) {
|
||||
this._dragDrop.callbacks.dragStart = this._onDragStart;
|
||||
this._dragDrop.callback.drop = this._onDrop;
|
||||
} else {
|
||||
this._dragDrop = this.options.dragDrop.map(d => {
|
||||
d.callbacks = {
|
||||
dragstart: this._onDragStart.bind(this),
|
||||
drop: this._onDrop.bind(this)
|
||||
};
|
||||
return new foundry.applications.ux.DragDrop.implementation(d);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dragStart event.
|
||||
* @param {DragEvent} event
|
||||
|
|
@ -472,7 +503,10 @@ export default function DHApplicationMixin(Base) {
|
|||
icon: 'fa-solid fa-explosion',
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
||||
return (
|
||||
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
||||
!foundry.utils.isEmpty(doc?.damage?.parts)
|
||||
);
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
|
|
@ -664,6 +698,9 @@ export default function DHApplicationMixin(Base) {
|
|||
case 'weapon':
|
||||
presets.folder = 'equipments.folders.weapons';
|
||||
break;
|
||||
case 'feature':
|
||||
presets.folder = 'features';
|
||||
break;
|
||||
case 'domainCard':
|
||||
presets.folder = 'domains';
|
||||
presets.filter = {
|
||||
|
|
|
|||
|
|
@ -251,6 +251,12 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
|
||||
/* If any noticeable slowdown occurs, consider replacing with enriching description on clicking to expand descriptions */
|
||||
for (const item of this.items) {
|
||||
if (['weapon', 'armor'].includes(item.type)) {
|
||||
item.system.enrichedTags = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/global/partials/item-tags.hbs',
|
||||
item.system
|
||||
);
|
||||
}
|
||||
item.system.enrichedDescription =
|
||||
(await item.system.getEnrichedDescription?.()) ??
|
||||
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
||||
|
|
|
|||
|
|
@ -52,46 +52,6 @@
|
|||
* @extends {foundry.applications.ux.ContextMenu}
|
||||
*/
|
||||
export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
||||
/**
|
||||
* @param {HTMLElement|jQuery} container - The HTML element that contains the context menu targets.
|
||||
* @param {string} selector - A CSS selector which activates the context menu.
|
||||
* @param {ContextMenuEntry[]} menuItems - An Array of entries to display in the menu
|
||||
* @param {ContextMenuOptions} [options] - Additional options to configure the context menu.
|
||||
*/
|
||||
constructor(container, selector, menuItems, options) {
|
||||
super(container, selector, menuItems, options);
|
||||
|
||||
/** @deprecated since v13 until v15 */
|
||||
this.#jQuery = options.jQuery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to pass jQuery objects or HTMLElement instances to callback.
|
||||
* @type {boolean}
|
||||
*/
|
||||
#jQuery;
|
||||
|
||||
/**@inheritdoc */
|
||||
activateListeners(menu) {
|
||||
menu.addEventListener('click', this.#onClickItem.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click events on context menu items.
|
||||
* @param {PointerEvent} event The click event
|
||||
*/
|
||||
#onClickItem(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const element = event.target.closest('.context-item');
|
||||
if (!element) return;
|
||||
const item = this.menuItems.find(i => i.element === element);
|
||||
item?.onClick(event, this.#jQuery ? $(this.target) : this.target);
|
||||
this.close();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Trigger a context menu event in response to a normal click on a additional options button.
|
||||
* @param {PointerEvent} event
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue