mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-13 20:21:06 +01:00
Merge branch 'development' of https://github.com/Foundryborne/daggerheart into feature/party-sheet
This commit is contained in:
commit
1a0c6f46bc
648 changed files with 7471 additions and 3950 deletions
|
|
@ -2,8 +2,10 @@ export * as characterCreation from './characterCreation/_module.mjs';
|
|||
export * as dialogs from './dialogs/_module.mjs';
|
||||
export * as hud from './hud/_module.mjs';
|
||||
export * as levelup from './levelup/_module.mjs';
|
||||
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';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import { ItemBrowser } from '../ui/itemBrowser.mjs';
|
||||
import { createEmbeddedItemsWithEffects, createEmbeddedItemWithEffects } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -46,8 +45,6 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
};
|
||||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
|
||||
this.itemBrowser = null;
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -425,26 +422,30 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
equipment = ['armor', 'weapon'];
|
||||
|
||||
const presets = {
|
||||
compendium: 'daggerheart',
|
||||
folder: equipment.includes(type) ? 'equipments' : type,
|
||||
folder: equipment.includes(type) ? `equipments.folders.${type}s` : type,
|
||||
render: {
|
||||
noFolder: true
|
||||
}
|
||||
};
|
||||
|
||||
if (type == 'domains')
|
||||
if (type === 'domains')
|
||||
presets.filter = {
|
||||
'level.max': { key: 'level.max', value: 1 },
|
||||
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
||||
};
|
||||
|
||||
if (type === 'subclasses')
|
||||
presets.filter = {
|
||||
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid }
|
||||
};
|
||||
|
||||
if (equipment.includes(type))
|
||||
presets.filter = {
|
||||
'system.tier': { key: 'system.tier', value: 1 },
|
||||
'type': { key: 'type', value: type }
|
||||
};
|
||||
|
||||
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
|
||||
ui.compendiumBrowser.open(presets);
|
||||
}
|
||||
|
||||
static async viewItem(_, target) {
|
||||
|
|
@ -562,7 +563,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
{ overwrite: true }
|
||||
);
|
||||
|
||||
if (this.itemBrowser) this.itemBrowser.close();
|
||||
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
|
||||
this.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -7,7 +9,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.roll = roll;
|
||||
this.config = config;
|
||||
this.config.experiences = [];
|
||||
this.reactionOverride = config.roll?.type === 'reaction';
|
||||
this.reactionOverride = config.actionType === 'reaction';
|
||||
|
||||
if (config.source?.action) {
|
||||
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
||||
|
|
@ -20,7 +22,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'roll-selection',
|
||||
// id: 'roll-selection',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
||||
position: {
|
||||
width: 'auto'
|
||||
|
|
@ -42,7 +44,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
};
|
||||
|
||||
get title() {
|
||||
return this.config.title;
|
||||
return `${this.config.title}${this.actor ? `: ${this.actor.name}` : ''}`;
|
||||
}
|
||||
|
||||
get actor() {
|
||||
|
|
@ -81,7 +83,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
);
|
||||
context.costs = updatedCosts.map(x => ({
|
||||
...x,
|
||||
label: x.keyIsID
|
||||
label: x.itemId
|
||||
? this.action.parent.parent.name
|
||||
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
||||
}));
|
||||
|
|
@ -113,15 +115,24 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
if (this.actor.system.traits) context.abilities = this.getTraitModifiers();
|
||||
|
||||
context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll';
|
||||
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
||||
context.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
getTraitModifiers() {
|
||||
return Object.values(abilities).map(a => ({
|
||||
id: a.id,
|
||||
label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})`
|
||||
}));
|
||||
}
|
||||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (this.config.costs) {
|
||||
|
|
@ -133,6 +144,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.roll[key] = value;
|
||||
});
|
||||
}
|
||||
if (rest.hasOwnProperty('trait')) {
|
||||
this.config.roll.trait = rest.trait;
|
||||
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||
});
|
||||
}
|
||||
this.config.extraFormula = rest.extraFormula;
|
||||
this.render();
|
||||
}
|
||||
|
|
@ -151,31 +168,29 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.config.experiences.indexOf(button.dataset.key) > -1
|
||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||
: [...this.config.experiences, button.dataset.key];
|
||||
if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
|
||||
this.config.costs =
|
||||
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
||||
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
|
||||
: [
|
||||
...this.config.costs,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
}
|
||||
this.config.costs =
|
||||
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
||||
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
|
||||
: [
|
||||
...this.config.costs,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleReaction() {
|
||||
if (this.config.roll) {
|
||||
this.reactionOverride = !this.reactionOverride;
|
||||
this.config.roll.type = this.reactionOverride
|
||||
this.config.actionType = this.reactionOverride
|
||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
: this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
? null
|
||||
: this.config.roll.type;
|
||||
: this.config.actionType;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart']
|
||||
classes: ['daggerheart'],
|
||||
actions: {
|
||||
combat: DHTokenHUD.#onToggleCombat
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
|
|
@ -11,8 +14,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}
|
||||
};
|
||||
|
||||
static #nonCombatTypes = ['environment', 'companion'];
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
||||
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||
? false
|
||||
: context.canToggleCombat;
|
||||
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||
const effect = context.statusEffects[key];
|
||||
if (effect.systemEffect) acc[key] = effect;
|
||||
|
|
@ -36,6 +45,20 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
return context;
|
||||
}
|
||||
|
||||
static async #onToggleCombat() {
|
||||
const tokens = canvas.tokens.controlled
|
||||
.filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||
.map(t => t.document);
|
||||
if (!this.object.controlled) tokens.push(this.document);
|
||||
|
||||
try {
|
||||
if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
|
||||
else await TokenDocument.implementation.createCombatants(tokens);
|
||||
} catch (err) {
|
||||
ui.notifications.warn(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
_getStatusEffectChoices() {
|
||||
// Include all HUD-enabled status effects
|
||||
const choices = {};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { abilities, subclassFeatureLabels } from '../../config/actorConfig.mjs';
|
||||
import { getDeleteKeys, tagifyElement } from '../../helpers/utils.mjs';
|
||||
import { ItemBrowser } from '../ui/itemBrowser.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -12,8 +11,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
this._dragDrop = this._createDragDropHandlers();
|
||||
this.tabGroups.primary = 'advancements';
|
||||
|
||||
this.itemBrowser = null;
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -540,7 +537,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const type = target.dataset.compendium ?? target.dataset.type;
|
||||
|
||||
const presets = {
|
||||
compendium: 'daggerheart',
|
||||
folder: type,
|
||||
render: {
|
||||
noFolder: true
|
||||
|
|
@ -559,7 +555,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
};
|
||||
}
|
||||
|
||||
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
|
||||
ui.compendiumBrowser.open(presets);
|
||||
}
|
||||
|
||||
static async selectPreview(_, button) {
|
||||
|
|
@ -662,7 +658,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
}, {});
|
||||
|
||||
await this.actor.levelUp(levelupData);
|
||||
if (this.itemBrowser) this.itemBrowser.close();
|
||||
|
||||
if (ui.compendiumBrowser) ui.compendiumBrowser.close();
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
module/applications/scene/_module.mjs
Normal file
1
module/applications/scene/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as DhSceneConfigSettings } from './sceneConfigSettings.mjs';
|
||||
24
module/applications/scene/sceneConfigSettings.mjs
Normal file
24
module/applications/scene/sceneConfigSettings.mjs
Normal file
|
|
@ -0,0 +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
|
||||
};
|
||||
return tmpParts;
|
||||
}
|
||||
|
||||
static PARTS = DhSceneConfigSettings.buildParts();
|
||||
|
||||
static buildTabs() {
|
||||
super.TABS.sheet.tabs.push({ id: 'dh', icon: 'fa-solid' });
|
||||
return super.TABS;
|
||||
}
|
||||
|
||||
static TABS = DhSceneConfigSettings.buildTabs();
|
||||
}
|
||||
|
|
@ -3,43 +3,48 @@ import { getDiceSoNicePreset } from '../../config/generalConfig.mjs';
|
|||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
/**
|
||||
* @import {ApplicationClickAction} from "@client/applications/_types.mjs"
|
||||
*/
|
||||
|
||||
export default class DHAppearanceSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor() {
|
||||
super({});
|
||||
|
||||
this.settings = new DhAppearance(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).toObject()
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'daggerheart-appearance-settings',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'setting'],
|
||||
position: { width: '600', height: 'auto' },
|
||||
window: {
|
||||
title: 'DAGGERHEART.SETTINGS.Menu.title',
|
||||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
reset: this.reset,
|
||||
save: this.save,
|
||||
preview: this.preview
|
||||
reset: DHAppearanceSettings.#onReset,
|
||||
preview: DHAppearanceSettings.#onPreview
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
form: {
|
||||
closeOnSubmit: true,
|
||||
handler: DHAppearanceSettings.#onSubmit
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: 'systems/daggerheart/templates/settings/appearance-settings.hbs'
|
||||
}
|
||||
header: { template: 'systems/daggerheart/templates/settings/appearance-settings/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
main: { template: 'systems/daggerheart/templates/settings/appearance-settings/main.hbs' },
|
||||
diceSoNice: { template: 'systems/daggerheart/templates/settings/appearance-settings/diceSoNice.hbs' },
|
||||
footer: { template: 'templates/generic/form-footer.hbs' }
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
general: {
|
||||
tabs: [
|
||||
{ id: 'main', label: 'DAGGERHEART.GENERAL.Tabs.general' },
|
||||
{ id: 'diceSoNice', label: 'DAGGERHEART.SETTINGS.Menu.appearance.diceSoNice.title' }
|
||||
],
|
||||
initial: 'main'
|
||||
},
|
||||
diceSoNice: {
|
||||
tabs: [
|
||||
{ id: 'hope', label: 'DAGGERHEART.GENERAL.hope' },
|
||||
|
|
@ -51,79 +56,149 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
};
|
||||
|
||||
changeTab(tab, group, options) {
|
||||
super.changeTab(tab, group, options);
|
||||
/**@type {DhAppearance}*/
|
||||
setting;
|
||||
|
||||
this.render();
|
||||
static #localized = false;
|
||||
|
||||
/** @inheritDoc */
|
||||
async _preFirstRender(_context, _options) {
|
||||
await super._preFirstRender(_context, _options);
|
||||
if (!DHAppearanceSettings.#localized) {
|
||||
foundry.helpers.Localization.localizeDataModel(this.setting.constructor);
|
||||
DHAppearanceSettings.#localized = true;
|
||||
}
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.settingFields = this.settings;
|
||||
|
||||
context.showDiceSoNice = game.modules.get('dice-so-nice')?.active;
|
||||
if (game.dice3d) {
|
||||
context.diceSoNiceTextures = game.dice3d.exports.TEXTURELIST;
|
||||
context.diceSoNiceColorsets = game.dice3d.exports.COLORSETS;
|
||||
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).map(key => ({
|
||||
key: key,
|
||||
name: `DICESONICE.Material${key.capitalize()}`
|
||||
}));
|
||||
context.diceSoNiceSystems = [];
|
||||
for (const [key, system] of game.dice3d.DiceFactory.systems.entries()) {
|
||||
context.diceSoNiceSystems.push({ key, name: system.name });
|
||||
}
|
||||
/** @inheritdoc */
|
||||
_configureRenderParts(options) {
|
||||
const parts = super._configureRenderParts(options);
|
||||
if (!game.modules.get('dice-so-nice')?.active) {
|
||||
delete parts.diceSoNice;
|
||||
delete parts.tabs;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
context.diceTab = {
|
||||
key: this.tabGroups.diceSoNice,
|
||||
source: this.settings._source.diceSoNice[this.tabGroups.diceSoNice],
|
||||
fields: this.settings.schema.fields.diceSoNice.fields[this.tabGroups.diceSoNice].fields
|
||||
};
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (options.isFirstRender)
|
||||
this.setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
|
||||
context.setting = this.setting;
|
||||
context.fields = this.setting.schema.fields;
|
||||
|
||||
context.tabs = this._prepareTabs('general');
|
||||
context.dsnTabs = this._prepareTabs('diceSoNice');
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async updateData(event, element, formData) {
|
||||
const updatedSettings = foundry.utils.expandObject(formData.object);
|
||||
|
||||
await this.settings.updateSource(updatedSettings);
|
||||
this.render();
|
||||
/**@inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
const partContext = await super._preparePartContext(partId, context, options);
|
||||
if (partId in context.tabs) partContext.tab = partContext.tabs[partId];
|
||||
switch (partId) {
|
||||
case 'diceSoNice':
|
||||
await this.prepareDiceSoNiceContext(partContext);
|
||||
break;
|
||||
case 'footer':
|
||||
partContext.buttons = [
|
||||
{ type: 'button', action: 'reset', icon: 'fa-solid fa-arrow-rotate-left', label: 'Reset' },
|
||||
{ type: 'submit', icon: 'fa-solid fa-floppy-disk', label: 'Save Changes' }
|
||||
];
|
||||
break;
|
||||
}
|
||||
return partContext;
|
||||
}
|
||||
|
||||
static async preview() {
|
||||
const source = this.settings._source.diceSoNice[this.tabGroups.diceSoNice];
|
||||
let faces = 'd12';
|
||||
switch (this.tabGroups.diceSoNice) {
|
||||
case 'advantage':
|
||||
case 'disadvantage':
|
||||
faces = 'd6';
|
||||
}
|
||||
const preset = await getDiceSoNicePreset(source, faces);
|
||||
const diceSoNiceRoll = await new Roll(`1${faces}`).evaluate();
|
||||
/**
|
||||
* Prepare render context for the DSN part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async prepareDiceSoNiceContext(context) {
|
||||
context.diceSoNiceTextures = Object.entries(game.dice3d.exports.TEXTURELIST).reduce(
|
||||
(acc, [k, v]) => ({
|
||||
...acc,
|
||||
[k]: v.name
|
||||
}),
|
||||
{}
|
||||
);
|
||||
context.diceSoNiceColorsets = Object.values(game.dice3d.exports.COLORSETS).reduce(
|
||||
(acc, v) => ({
|
||||
...acc,
|
||||
[v.id]: v.description
|
||||
}),
|
||||
{}
|
||||
);
|
||||
context.diceSoNiceMaterials = Object.keys(game.dice3d.DiceFactory.material_options).reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: `DICESONICE.Material${key.capitalize()}`
|
||||
}),
|
||||
{}
|
||||
);
|
||||
context.diceSoNiceSystems = Object.fromEntries(
|
||||
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
|
||||
);
|
||||
|
||||
foundry.utils.mergeObject(
|
||||
context.dsnTabs,
|
||||
['hope', 'fear', 'advantage', 'disadvantage'].reduce(
|
||||
(acc, key) => ({
|
||||
...acc,
|
||||
[key]: {
|
||||
values: this.setting.diceSoNice[key],
|
||||
fields: this.setting.schema.getField(`diceSoNice.${key}`).fields
|
||||
}
|
||||
}),
|
||||
{}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the configuration form.
|
||||
* @this {DHAppearanceSettings}
|
||||
* @param {SubmitEvent} event
|
||||
* @param {HTMLFormElement} form
|
||||
* @param {foundry.applications.ux.FormDataExtended} formData
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Submit the configuration form.
|
||||
* @this {DHAppearanceSettings}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onPreview(_, target) {
|
||||
const formData = new foundry.applications.ux.FormDataExtended(target.closest('form'));
|
||||
const { diceSoNice } = 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;
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
||||
}
|
||||
|
||||
static async reset() {
|
||||
this.settings = new DhAppearance();
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async save() {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance, this.settings.toObject());
|
||||
|
||||
this.close();
|
||||
}
|
||||
|
||||
_getTabs(tabs) {
|
||||
for (const v of Object.values(tabs)) {
|
||||
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
||||
v.cssClass = v.active ? 'active' : '';
|
||||
}
|
||||
|
||||
return tabs;
|
||||
/**
|
||||
* Reset the form back to default values.
|
||||
* @this {DHAppearanceSettings}
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #onReset() {
|
||||
this.setting = new this.setting.constructor();
|
||||
this.render({ force: false });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
|||
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
||||
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
|
||||
roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
main: {
|
||||
tabs: [{ id: 'general' }, { id: 'rules' }],
|
||||
tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
|
||||
initial: 'general',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
||||
import { slugify } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class DhHomebrewSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -10,11 +11,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).toObject()
|
||||
);
|
||||
|
||||
this.selected = {
|
||||
domain: null
|
||||
};
|
||||
this.selected = this.#getDefaultAdversaryType();
|
||||
}
|
||||
|
||||
#getDefaultAdversaryType = () => ({
|
||||
domain: null,
|
||||
adversaryType: null
|
||||
});
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.SETTINGS.Menu.title');
|
||||
}
|
||||
|
|
@ -35,6 +39,9 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
addDomain: this.addDomain,
|
||||
toggleSelectedDomain: this.toggleSelectedDomain,
|
||||
deleteDomain: this.deleteDomain,
|
||||
addAdversaryType: this.addAdversaryType,
|
||||
deleteAdversaryType: this.deleteAdversaryType,
|
||||
selectAdversaryType: this.selectAdversaryType,
|
||||
save: this.save,
|
||||
reset: this.reset
|
||||
},
|
||||
|
|
@ -45,6 +52,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
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' }
|
||||
};
|
||||
|
|
@ -52,12 +61,19 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
main: {
|
||||
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'downtime' }],
|
||||
tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
|
||||
initial: 'settings',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
changeTab(tab, group, options) {
|
||||
super.changeTab(tab, group, options);
|
||||
this.selected = this.#getDefaultAdversaryType();
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.settingFields = this.settings;
|
||||
|
|
@ -79,6 +95,11 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
context.configDomains = CONFIG.DH.DOMAIN.domains;
|
||||
context.homebrewDomains = this.settings.domains;
|
||||
break;
|
||||
case 'types':
|
||||
context.selectedAdversaryType = this.selected.adversaryType
|
||||
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
|
||||
: null;
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
|
|
@ -95,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,
|
||||
|
|
@ -129,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();
|
||||
}
|
||||
|
|
@ -301,6 +346,32 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async addAdversaryType(_, target) {
|
||||
const newId = foundry.utils.randomID();
|
||||
await this.settings.updateSource({
|
||||
[`adversaryTypes.${newId}`]: {
|
||||
id: newId,
|
||||
label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.adversaryType.newType')
|
||||
}
|
||||
});
|
||||
|
||||
this.selected.adversaryType = newId;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async deleteAdversaryType(_, target) {
|
||||
const { key } = target.dataset;
|
||||
await this.settings.updateSource({ [`adversaryTypes.-=${key}`]: null });
|
||||
|
||||
this.selected.adversaryType = this.selected.adversaryType === key ? null : this.selected.adversaryType;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async selectAdversaryType(_, target) {
|
||||
this.selected.adversaryType = this.selected.adversaryType === target.dataset.type ? null : target.dataset.type;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async save() {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.close();
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -132,12 +132,19 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
||||
const resource = this.action.parent.resource;
|
||||
if (resource) {
|
||||
options[this.action.parent.parent.id] = {
|
||||
options.resource = {
|
||||
label: 'DAGGERHEART.GENERAL.itemResource',
|
||||
group: 'Global'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata?.isQuantifiable) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
};
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
@ -164,13 +171,14 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|||
|
||||
_prepareSubmitData(_event, formData) {
|
||||
const submitData = foundry.utils.expandObject(formData.object);
|
||||
|
||||
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
|
||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||
const data = foundry.utils.getProperty(submitData, keyPath);
|
||||
const dataValues = data ? Object.values(data) : [];
|
||||
if (keyPath === 'cost') {
|
||||
for (var value of dataValues) {
|
||||
const item = this.action.parent.parent.id === value.key;
|
||||
value.keyIsID = Boolean(item);
|
||||
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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 });
|
||||
});
|
||||
|
|
@ -25,11 +25,22 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
};
|
||||
|
||||
static PARTS = {
|
||||
sidebar: { template: 'systems/daggerheart/templates/sheets/actors/adversary/sidebar.hbs' },
|
||||
sidebar: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/sidebar.hbs',
|
||||
scrollable: ['.shortcut-items-section']
|
||||
},
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/adversary/header.hbs' },
|
||||
features: { template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs' },
|
||||
notes: { template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs' },
|
||||
effects: { template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs' }
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs',
|
||||
scrollable: ['.feature-section']
|
||||
},
|
||||
notes: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs'
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs',
|
||||
scrollable: ['.effects-sections']
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
|
|
@ -45,6 +56,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +66,9 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
|
||||
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
|
||||
context.adversaryType = game.i18n.localize(adversaryTypes[this.document.system.type].label);
|
||||
break;
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
|
|
@ -131,9 +146,9 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
title: `Reaction Roll: ${this.actor.name}`,
|
||||
headerTitle: 'Adversary Reaction Roll',
|
||||
roll: {
|
||||
type: 'reaction'
|
||||
type: 'trait'
|
||||
},
|
||||
type: 'trait',
|
||||
actionType: 'reaction',
|
||||
hasRoll: true,
|
||||
data: this.actor.getRollData()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
|||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
|
|
@ -15,6 +14,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['character'],
|
||||
position: { width: 850, height: 800 },
|
||||
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
|
||||
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
|
||||
actions: {
|
||||
toggleVault: CharacterSheet.#toggleVault,
|
||||
rollAttribute: CharacterSheet.#rollAttribute,
|
||||
|
|
@ -27,8 +28,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
useDowntime: this.useDowntime,
|
||||
tempBrowser: CharacterSheet.#tempBrowser
|
||||
useDowntime: this.useDowntime
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
|
|
@ -78,6 +78,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
static PARTS = {
|
||||
sidebar: {
|
||||
id: 'sidebar',
|
||||
scrollable: ['.shortcut-items-section'],
|
||||
template: 'systems/daggerheart/templates/sheets/actors/character/sidebar.hbs'
|
||||
},
|
||||
header: {
|
||||
|
|
@ -86,22 +87,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
},
|
||||
features: {
|
||||
id: 'features',
|
||||
scrollable: ['.features-sections'],
|
||||
template: 'systems/daggerheart/templates/sheets/actors/character/features.hbs'
|
||||
},
|
||||
loadout: {
|
||||
id: 'loadout',
|
||||
scrollable: ['.items-section'],
|
||||
template: 'systems/daggerheart/templates/sheets/actors/character/loadout.hbs'
|
||||
},
|
||||
inventory: {
|
||||
id: 'inventory',
|
||||
scrollable: ['.items-section'],
|
||||
template: 'systems/daggerheart/templates/sheets/actors/character/inventory.hbs'
|
||||
},
|
||||
biography: {
|
||||
id: 'biography',
|
||||
scrollable: ['.items-section'],
|
||||
template: 'systems/daggerheart/templates/sheets/actors/character/biography.hbs'
|
||||
},
|
||||
effects: {
|
||||
id: 'effects',
|
||||
scrollable: ['.effects-sections'],
|
||||
template: 'systems/daggerheart/templates/sheets/actors/character/effects.hbs'
|
||||
}
|
||||
};
|
||||
|
|
@ -126,6 +132,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||
element.addEventListener('click', e => e.stopPropagation());
|
||||
});
|
||||
|
||||
// Add listener for armor marks input
|
||||
|
|
@ -142,6 +149,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
.querySelector('.level-value')
|
||||
?.addEventListener('change', event => this.document.updateLevel(Number(event.currentTarget.value)));
|
||||
|
||||
const observer = this.document.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER, {
|
||||
exact: true
|
||||
});
|
||||
if (observer) {
|
||||
this.element.querySelector('.window-content').classList.add('viewMode');
|
||||
}
|
||||
|
||||
this._createFilterMenus();
|
||||
this._createSearchFilter();
|
||||
}
|
||||
|
|
@ -218,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -620,14 +634,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const { key } = button.dataset;
|
||||
|
||||
const presets = {
|
||||
compendium: 'daggerheart',
|
||||
folder: key,
|
||||
filter:
|
||||
key === 'subclasses'
|
||||
? {
|
||||
'system.linkedClass.uuid': {
|
||||
key: 'system.linkedClass.uuid',
|
||||
value: this.document.system.class.value._stats.compendiumSource
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
render: {
|
||||
noFolder: true
|
||||
}
|
||||
};
|
||||
|
||||
return new ItemBrowser({ presets }).render({ force: true });
|
||||
ui.compendiumBrowser.open(presets);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -655,31 +677,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
})
|
||||
});
|
||||
|
||||
this.consumeResource(result?.costs);
|
||||
}
|
||||
|
||||
// Remove when Action Refactor part #2 done
|
||||
async consumeResource(costs) {
|
||||
if (!costs?.length) return;
|
||||
const usefulResources = {
|
||||
...foundry.utils.deepClone(this.actor.system.resources),
|
||||
fear: {
|
||||
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
reversed: false
|
||||
}
|
||||
};
|
||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
|
||||
const resource = usefulResources[c.key];
|
||||
return {
|
||||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
target: resource.target,
|
||||
keyIsID: resource.keyIsID
|
||||
};
|
||||
});
|
||||
|
||||
await this.actor.modifyResource(resources);
|
||||
if (result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
|
||||
}
|
||||
|
||||
//TODO: redo toggleEquipItem method
|
||||
|
|
@ -724,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();
|
||||
}
|
||||
|
||||
|
|
@ -768,13 +766,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Temp
|
||||
*/
|
||||
static async #tempBrowser(_, target) {
|
||||
new ItemBrowser().render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the roll values of resource dice.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
classes: ['actor', 'companion'],
|
||||
position: { width: 340 },
|
||||
actions: {
|
||||
actionRoll: DhCompanionSheet.#actionRoll,
|
||||
levelManagement: DhCompanionSheet.#levelManagement
|
||||
}
|
||||
};
|
||||
|
|
@ -15,7 +16,10 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/companion/header.hbs' },
|
||||
details: { template: 'systems/daggerheart/templates/sheets/actors/companion/details.hbs' },
|
||||
effects: { template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs' }
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/companion/effects.hbs',
|
||||
scrollable: ['.effects-sections']
|
||||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -42,6 +46,51 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static async #actionRoll(event) {
|
||||
const partner = this.actor.system.partner;
|
||||
const config = {
|
||||
event,
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}: ${this.actor.name}`,
|
||||
headerTitle: `Companion ${game.i18n.localize('DAGGERHEART.GENERAL.Roll.action')}`,
|
||||
roll: {
|
||||
trait: partner.system.spellcastModifierTrait?.key
|
||||
},
|
||||
hasRoll: true,
|
||||
data: partner.getRollData()
|
||||
};
|
||||
|
||||
const result = await partner.diceRoll(config);
|
||||
this.consumeResource(result?.costs);
|
||||
}
|
||||
|
||||
// Remove when Action Refactor part #2 done
|
||||
async consumeResource(costs) {
|
||||
if (!costs?.length) return;
|
||||
|
||||
const partner = this.actor.system.partner;
|
||||
const usefulResources = {
|
||||
...foundry.utils.deepClone(partner.system.resources),
|
||||
fear: {
|
||||
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
reversed: false
|
||||
}
|
||||
};
|
||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
|
||||
const resource = usefulResources[c.key];
|
||||
return {
|
||||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
target: resource.target
|
||||
};
|
||||
});
|
||||
|
||||
await partner.modifyResource(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the companions level management window.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,13 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
/**@override */
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
|
||||
features: { template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs' },
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs',
|
||||
scrollable: ['feature-section']
|
||||
},
|
||||
potentialAdversaries: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs'
|
||||
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs',
|
||||
scrollable: ['items-sections']
|
||||
},
|
||||
notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
import { getDocFromElement, getDocFromElementSync, tagifyElement } from '../../../helpers/utils.mjs';
|
||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||
|
||||
const typeSettingsMap = {
|
||||
character: 'extendCharacterDescriptions',
|
||||
|
|
@ -412,6 +411,19 @@ export default function DHApplicationMixin(Base) {
|
|||
];
|
||||
|
||||
if (usable) {
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.cancelBeastform',
|
||||
icon: 'fa-solid fa-ban',
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system?.actions?.some(a => a.type === 'beastform');
|
||||
},
|
||||
callback: async target =>
|
||||
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(
|
||||
await getDocFromElement(target)
|
||||
)
|
||||
});
|
||||
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
|
|
@ -422,7 +434,9 @@ export default function DHApplicationMixin(Base) {
|
|||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
return action && action.use(event, { byPassRoll: true });
|
||||
const config = action.prepareConfig(event);
|
||||
config.hasRoll = false;
|
||||
return action && action.workflow.get('damage').execute(config, null, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -441,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)
|
||||
|
|
@ -577,28 +591,27 @@ export default function DHApplicationMixin(Base) {
|
|||
static async #browseItem(event, target) {
|
||||
const type = target.dataset.compendium ?? target.dataset.type;
|
||||
|
||||
const presets = {};
|
||||
const presets = {
|
||||
render: {
|
||||
noFolder: true
|
||||
}
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'loot':
|
||||
presets.folder = 'equipments.folders.loots';
|
||||
break;
|
||||
case 'consumable':
|
||||
presets.folder = 'equipments.folders.consumables';
|
||||
break;
|
||||
case 'armor':
|
||||
presets.folder = 'equipments.folders.armors';
|
||||
break;
|
||||
case 'weapon':
|
||||
presets.compendium = 'daggerheart';
|
||||
presets.folder = 'equipments';
|
||||
presets.render = {
|
||||
noFolder: true
|
||||
};
|
||||
presets.filter = {
|
||||
type: { key: 'type', value: type, forced: true }
|
||||
};
|
||||
presets.folder = 'equipments.folders.weapons';
|
||||
break;
|
||||
case 'domainCard':
|
||||
presets.compendium = 'daggerheart';
|
||||
presets.folder = 'domains';
|
||||
presets.render = {
|
||||
noFolder: true
|
||||
};
|
||||
presets.filter = {
|
||||
'level.max': { key: 'level.max', value: this.document.system.levelData.level.current },
|
||||
'system.domain': { key: 'system.domain', value: this.document.system.domains }
|
||||
|
|
@ -608,7 +621,7 @@ export default function DHApplicationMixin(Base) {
|
|||
return;
|
||||
}
|
||||
|
||||
return new ItemBrowser({ presets }).render({ force: true });
|
||||
ui.compendiumBrowser.open(presets);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -639,7 +652,6 @@ export default function DHApplicationMixin(Base) {
|
|||
if (featureOnCharacter) {
|
||||
systemData = {
|
||||
originItemType: this.document.type,
|
||||
originId: this.document.id,
|
||||
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -167,12 +167,12 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
const { type } = target.dataset;
|
||||
const cls = foundry.documents.Item.implementation;
|
||||
|
||||
const multiclass = this.document.system.isMulticlass ? 'multiclass' : null;
|
||||
let systemData = {};
|
||||
if (this.document.parent?.type === 'character') {
|
||||
systemData = {
|
||||
originItemType: this.document.type,
|
||||
originId: this.document.id,
|
||||
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
||||
identifier: multiclass ?? type
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -293,14 +293,15 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
|
||||
if (this.document.parent?.type === 'character') {
|
||||
const itemData = item.toObject();
|
||||
const multiclass = this.document.system.isMulticlass ? 'multiclass' : null;
|
||||
item = await cls.create(
|
||||
{
|
||||
...itemData,
|
||||
_stats: { compendiumSource: this.document.uuid },
|
||||
system: {
|
||||
...itemData.system,
|
||||
originItemType: this.document.type,
|
||||
originId: this.document.id,
|
||||
identifier: this.document.system.isMulticlass ? 'multiclass' : null
|
||||
identifier: multiclass ?? target.dataset.type
|
||||
}
|
||||
},
|
||||
{ parent: this.document.parent }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
template: 'systems/daggerheart/templates/sheets/items/class/settings.hbs',
|
||||
scrollable: ['.settings']
|
||||
},
|
||||
questions: {
|
||||
template: 'systems/daggerheart/templates/sheets/items/class/questions.hbs',
|
||||
scrollable: ['.questions']
|
||||
},
|
||||
effects: {
|
||||
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
|
||||
scrollable: ['.effects']
|
||||
|
|
@ -55,7 +59,13 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'description' }, { id: 'features' }, { id: 'settings' }, { id: 'effects' }],
|
||||
tabs: [
|
||||
{ id: 'description' },
|
||||
{ id: 'features' },
|
||||
{ id: 'settings' },
|
||||
{ id: 'questions' },
|
||||
{ id: 'effects' }
|
||||
],
|
||||
initial: 'description',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
@ -119,6 +129,15 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
const itemType = data.data ? data.type : item.type;
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (itemType === 'subclass') {
|
||||
if (item.system.linkedClass) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.subclassAlreadyLinked', {
|
||||
name: item.name,
|
||||
class: this.document.name
|
||||
})
|
||||
);
|
||||
}
|
||||
await item.update({ 'system.linkedClass': this.document.uuid });
|
||||
await this.document.update({
|
||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
|
|
@ -181,6 +200,12 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
static async #removeItemFromCollection(_event, element) {
|
||||
const { uuid, target } = element.dataset;
|
||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||
|
||||
if (target === 'subclasses') {
|
||||
const subclass = await foundry.utils.fromUuid(uuid);
|
||||
await subclass.update({ 'system.linkedClass': null });
|
||||
}
|
||||
|
||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i.uuid !== uuid).map(x => x.uuid) });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
|
|
|
|||
2
module/applications/sidebar/_module.mjs
Normal file
2
module/applications/sidebar/_module.mjs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
|
||||
export { default as DhSidebar } from './sidebar.mjs';
|
||||
33
module/applications/sidebar/sidebar.mjs
Normal file
33
module/applications/sidebar/sidebar.mjs
Normal 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;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
160
module/applications/sidebar/tabs/daggerheartMenu.mjs
Normal file
160
module/applications/sidebar/tabs/daggerheartMenu.mjs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,3 +3,4 @@ export { default as DhCombatTracker } from './combatTracker.mjs';
|
|||
export * as DhCountdowns from './countdowns.mjs';
|
||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||
export { default as DhHotbar } from './hotbar.mjs';
|
||||
export { ItemBrowser } from './itemBrowser.mjs';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
|
@ -55,21 +53,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
addChatListeners = async (app, html, data) => {
|
||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.target-save').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollSave(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.roll-all-save-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollAllSave(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollSimple(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.healing-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onHealing(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.ability-use-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
|
||||
);
|
||||
|
|
@ -90,80 +76,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
super.close(options);
|
||||
}
|
||||
|
||||
async getActor(uuid) {
|
||||
return await foundry.utils.fromUuid(uuid);
|
||||
}
|
||||
|
||||
getAction(actor, itemId, actionId) {
|
||||
const item = actor.items.get(itemId),
|
||||
action =
|
||||
actor.system.attack?._id === actionId
|
||||
? actor.system.attack
|
||||
: item.system.attack?._id === actionId
|
||||
? item.system.attack
|
||||
: item?.system?.actions?.get(actionId);
|
||||
return action;
|
||||
}
|
||||
|
||||
async onRollDamage(event, message) {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor);
|
||||
if(!actor.isOwner) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.rollDamage) return;
|
||||
await action.rollDamage(event, message);
|
||||
}
|
||||
}
|
||||
|
||||
async onRollSave(event, message) {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor),
|
||||
tokenId = event.target.closest('[data-token]')?.dataset.token,
|
||||
token = game.canvas.tokens.get(tokenId);
|
||||
if (!token?.actor || !token.isOwner) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.hasSave) return;
|
||||
action.rollSave(token.actor, event, message).then(result =>
|
||||
emitAsGM(
|
||||
GMUpdateEvent.UpdateSaveMessage,
|
||||
action.updateSaveMessage.bind(action, result, message, token.id),
|
||||
{
|
||||
action: action.uuid,
|
||||
message: message._id,
|
||||
token: token.id,
|
||||
result
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async onRollAllSave(event, message) {
|
||||
event.stopPropagation();
|
||||
if (!game.user.isGM) return;
|
||||
const targets = event.target.parentElement.querySelectorAll('[data-token] .target-save');
|
||||
const actor = await this.getActor(message.system.source.actor),
|
||||
action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
targets.forEach(async el => {
|
||||
const tokenId = el.closest('[data-token]')?.dataset.token,
|
||||
token = game.canvas.tokens.get(tokenId);
|
||||
if (!token.actor) return;
|
||||
if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
|
||||
else {
|
||||
token.actor.owner
|
||||
.query('reactionRoll', {
|
||||
actionId: action.uuid,
|
||||
actorId: token.actor.uuid,
|
||||
event,
|
||||
message
|
||||
})
|
||||
.then(result => action.updateSaveMessage(result, message, token.id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onRollSimple(event, message) {
|
||||
const buttonType = event.target.dataset.type ?? 'damage',
|
||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
||||
|
|
@ -197,8 +109,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
item.system.attack?.id === event.currentTarget.id
|
||||
? item.system.attack
|
||||
: item.system.actions.get(event.currentTarget.id);
|
||||
if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true });
|
||||
else action.use(event);
|
||||
if (event.currentTarget.dataset.directDamage) {
|
||||
const config = action.prepareConfig(event);
|
||||
config.hasRoll = false;
|
||||
action.workflow.get('damage').execute(config, null, true);
|
||||
} else action.use(event);
|
||||
}
|
||||
|
||||
async actionUseButton(event, message) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent, socketEvent } from "../../systemRegistration/socket.mjs";
|
||||
import { emitAsGM, GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
/** @override */
|
||||
async _preRender(context, options) {
|
||||
if (this.currentFear > this.maxFear)
|
||||
if (this.currentFear > this.maxFear && game.user.isGM)
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
||||
}
|
||||
|
||||
|
|
@ -106,19 +106,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
|
||||
async updateFear(value) {
|
||||
return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value);
|
||||
/* if(!game.user.isGM)
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateFear,
|
||||
update: value
|
||||
}
|
||||
});
|
||||
else
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
|
||||
/* if (!game.user.isGM) return;
|
||||
value = Math.max(0, Math.min(this.maxFear, value));
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
|
||||
return emitAsGM(
|
||||
GMUpdateEvent.UpdateFear,
|
||||
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,16 +15,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
this.fieldFilter = [];
|
||||
this.selectedMenu = { path: [], data: null };
|
||||
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
||||
this.presets = options.presets;
|
||||
|
||||
if (this.presets?.compendium && this.presets?.folder)
|
||||
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
|
||||
this.presets = {};
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'itemBrowser',
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'],
|
||||
tag: 'div',
|
||||
window: {
|
||||
frame: true,
|
||||
|
|
@ -84,17 +81,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
}
|
||||
};
|
||||
|
||||
/** @inheritDoc */
|
||||
async _preFirstRender(context, options) {
|
||||
if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600;
|
||||
|
||||
await super._preFirstRender(context, options);
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
async _preRender(context, options) {
|
||||
if (context.presets?.render?.noFolder || context.presets?.render?.lite)
|
||||
options.parts.splice(options.parts.indexOf('sidebar'), 1);
|
||||
this.presets = options.presets ?? {};
|
||||
|
||||
const width = this.presets?.render?.noFolder === true || this.presets?.render?.lite === true ? 600 : 850;
|
||||
if (this.rendered) this.setPosition({ width });
|
||||
else options.position.width = width;
|
||||
|
||||
await super._preRender(context, options);
|
||||
}
|
||||
|
|
@ -103,32 +96,31 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
this._createSearchFilter();
|
||||
this._createFilterInputs();
|
||||
this._createDragProcess();
|
||||
|
||||
if (context.presets?.render?.lite) this.element.classList.add('lite');
|
||||
|
||||
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder');
|
||||
|
||||
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter');
|
||||
|
||||
if (this.presets?.filter) {
|
||||
Object.entries(this.presets.filter).forEach(
|
||||
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value)
|
||||
this.element
|
||||
.querySelectorAll('[data-action="selectFolder"]')
|
||||
.forEach(element =>
|
||||
element.classList.toggle('is-selected', element.dataset.folderId === this.selectedMenu.path.join('.'))
|
||||
);
|
||||
await this._onInputFilterBrowser();
|
||||
}
|
||||
|
||||
this._createSearchFilter();
|
||||
|
||||
this.element.classList.toggle('lite', this.presets?.render?.lite === true);
|
||||
this.element.classList.toggle('no-folder', this.presets?.render?.noFolder === true);
|
||||
this.element.classList.toggle('no-filter', this.presets?.render?.noFilter === true);
|
||||
this.element.querySelectorAll('.folder-list > [data-action="selectFolder"]').forEach(element => {
|
||||
element.hidden =
|
||||
this.presets.render?.folders?.length && !this.presets.render.folders.includes(element.dataset.folderId);
|
||||
});
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement
|
||||
.querySelectorAll('[data-action="selectFolder"]')
|
||||
.forEach(element => element.addEventListener("contextmenu", (event) => {
|
||||
htmlElement.querySelectorAll('[data-action="selectFolder"]').forEach(element =>
|
||||
element.addEventListener('contextmenu', event => {
|
||||
event.target.classList.toggle('expanded');
|
||||
}))
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -139,22 +131,26 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.compendiums = this.getCompendiumFolders(foundry.utils.deepClone(this.config));
|
||||
// context.pathTitle = this.pathTile;
|
||||
context.menu = this.selectedMenu;
|
||||
context.formatLabel = this.formatLabel;
|
||||
context.formatChoices = this.formatChoices;
|
||||
context.fieldFilter = this.fieldFilter = this._createFieldFilter();
|
||||
context.items = this.items;
|
||||
context.presets = this.presets;
|
||||
return context;
|
||||
}
|
||||
|
||||
open(presets = {}) {
|
||||
this.presets = presets;
|
||||
ItemBrowser.selectFolder.call(this);
|
||||
}
|
||||
|
||||
getCompendiumFolders(config, parent = null, depth = 0) {
|
||||
let folders = [];
|
||||
Object.values(config).forEach(c => {
|
||||
// if(this.presets.render?.folders?.length && !this.presets.render.folders.includes(c.id)) return;
|
||||
const folder = {
|
||||
id: c.id,
|
||||
label: c.label,
|
||||
label: game.i18n.localize(c.label),
|
||||
selected: (!parent || parent.selected) && this.selectedMenu.path[depth] === c.id
|
||||
};
|
||||
folder.folders = c.folders
|
||||
|
|
@ -162,47 +158,108 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
: [];
|
||||
folders.push(folder);
|
||||
});
|
||||
folders.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
static async selectFolder(_, target, compend, folder) {
|
||||
const config = foundry.utils.deepClone(this.config),
|
||||
compendium = compend ?? target.closest('[data-compendium-id]').dataset.compendiumId,
|
||||
folderId = folder ?? target.dataset.folderId,
|
||||
folderPath = `${compendium}.folders.${folderId}`,
|
||||
folderData = foundry.utils.getProperty(config, folderPath);
|
||||
static async selectFolder(_, target) {
|
||||
const folderId = target?.dataset?.folderId ?? this.presets.folder,
|
||||
folderData = foundry.utils.getProperty(this.config, folderId) ?? {};
|
||||
|
||||
const columns = ItemBrowser.getFolderConfig(folderData).map(col => ({
|
||||
...col,
|
||||
label: game.i18n.localize(col.label)
|
||||
}));
|
||||
|
||||
this.selectedMenu = {
|
||||
path: folderPath.split('.'),
|
||||
path: folderId?.split('.') ?? [],
|
||||
data: {
|
||||
...folderData,
|
||||
columns: ItemBrowser.getFolderConfig(folderData)
|
||||
columns: columns
|
||||
}
|
||||
};
|
||||
|
||||
let items = [];
|
||||
for (const key of folderData.keys) {
|
||||
const comp = game.packs.get(`${compendium}.${key}`);
|
||||
if (!comp) return;
|
||||
items = items.concat(await comp.getDocuments({ type__in: folderData.type }));
|
||||
}
|
||||
await this.render({ force: true, presets: this.presets });
|
||||
|
||||
this.items = ItemBrowser.sortBy(items, 'name');
|
||||
|
||||
if(target) {
|
||||
target.closest('.compendium-sidebar').querySelectorAll('[data-action="selectFolder"]').forEach(element => element.classList.remove("is-selected"))
|
||||
target.classList.add('is-selected');
|
||||
}
|
||||
|
||||
this.render({ force: true });
|
||||
if (this.selectedMenu?.data?.type?.length) this.loadItems();
|
||||
}
|
||||
|
||||
_replaceHTML(result, content, options) {
|
||||
if(!options.isFirstRender) delete result.sidebar;
|
||||
if (!options.isFirstRender) delete result.sidebar;
|
||||
super._replaceHTML(result, content, options);
|
||||
}
|
||||
|
||||
loadItems() {
|
||||
let loadTimeout = this.toggleLoader(true);
|
||||
|
||||
const promises = [];
|
||||
|
||||
game.packs.forEach(pack => {
|
||||
promises.push(
|
||||
new Promise(async resolve => {
|
||||
const items = await pack.getDocuments({ type__in: this.selectedMenu?.data?.type });
|
||||
resolve(items);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(promises).then(async result => {
|
||||
this.items = ItemBrowser.sortBy(
|
||||
result.flatMap(r => r),
|
||||
'name'
|
||||
);
|
||||
this.fieldFilter = this._createFieldFilter();
|
||||
|
||||
if (this.presets?.filter) {
|
||||
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||
const filter = this.fieldFilter.find(c => c.name === k);
|
||||
if (filter) filter.value = v.value;
|
||||
});
|
||||
// await this._onInputFilterBrowser();
|
||||
}
|
||||
|
||||
const filterList = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/itemBrowser/filterContainer.hbs',
|
||||
{
|
||||
fieldFilter: this.fieldFilter,
|
||||
presets: this.presets,
|
||||
formatChoices: this.formatChoices
|
||||
}
|
||||
);
|
||||
|
||||
this.element.querySelector('.filter-content .wrapper').innerHTML = filterList;
|
||||
const filterContainer = this.element.querySelector('.filter-header > [data-action="expandContent"]');
|
||||
if (this.fieldFilter.length === 0) filterContainer.setAttribute('disabled', '');
|
||||
else filterContainer.removeAttribute('disabled');
|
||||
|
||||
const itemList = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
|
||||
{
|
||||
items: this.items,
|
||||
menu: this.selectedMenu,
|
||||
formatLabel: this.formatLabel
|
||||
}
|
||||
);
|
||||
|
||||
this.element.querySelector('.item-list').innerHTML = itemList;
|
||||
|
||||
this._createFilterInputs();
|
||||
await this._onInputFilterBrowser();
|
||||
this._createDragProcess();
|
||||
|
||||
clearTimeout(loadTimeout);
|
||||
this.toggleLoader(false);
|
||||
});
|
||||
}
|
||||
|
||||
toggleLoader(state) {
|
||||
const container = this.element.querySelector('.item-list');
|
||||
return setTimeout(() => {
|
||||
container.classList.toggle('loader', state);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
static expandContent(_, target) {
|
||||
const parent = target.parentElement;
|
||||
parent.classList.toggle('expanded');
|
||||
|
|
@ -235,8 +292,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
filters.forEach(f => {
|
||||
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
||||
else if (typeof f.choices === 'function') {
|
||||
f.choices = f.choices();
|
||||
f.choices = f.choices(this.items);
|
||||
}
|
||||
|
||||
// Clear field label so template uses our custom label parameter
|
||||
if (f.field && f.label) {
|
||||
f.field.label = undefined;
|
||||
}
|
||||
|
||||
f.name ??= f.key;
|
||||
f.value = this.presets?.filter?.[f.name]?.value ?? null;
|
||||
});
|
||||
|
|
@ -248,11 +311,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create and initialize search filter instances for the inventory and loadout sections.
|
||||
* Create and initialize search filter instance.
|
||||
*
|
||||
* Sets up two {@link foundry.applications.ux.SearchFilter} instances:
|
||||
* - One for the inventory, which filters items in the inventory grid.
|
||||
* - One for the loadout, which filters items in the loadout/card grid.
|
||||
* @private
|
||||
*/
|
||||
_createSearchFilter() {
|
||||
|
|
@ -317,6 +377,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
for (const li of html.querySelectorAll('.item-container')) {
|
||||
const itemUUID = li.dataset.itemUuid,
|
||||
item = this.items.find(i => i.uuid === itemUUID);
|
||||
if (!item) continue;
|
||||
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||
if (matchesSearch) this.#filteredItems.browser.search.add(item.id);
|
||||
const { input } = this.#filteredItems.browser;
|
||||
|
|
@ -408,11 +469,13 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
|
||||
const newOrder = [...itemList].reverse().sort((a, b) => {
|
||||
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
||||
bProp = b.querySelector(`[data-item-key="${key}"]`);
|
||||
bProp = b.querySelector(`[data-item-key="${key}"]`),
|
||||
aValue = isNaN(aProp.innerText) ? aProp.innerText : Number(aProp.innerText),
|
||||
bValue = isNaN(bProp.innerText) ? bProp.innerText : Number(bProp.innerText);
|
||||
if (type === 'DESC') {
|
||||
return aProp.innerText < bProp.innerText ? 1 : -1;
|
||||
return aValue < bValue ? 1 : -1;
|
||||
} else {
|
||||
return aProp.innerText > bProp.innerText ? 1 : -1;
|
||||
return aValue > bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -441,4 +504,41 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
_canDragStart() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static injectSidebarButton(html) {
|
||||
if (!game.user.isGM) return;
|
||||
const sectionId = html.dataset.tab,
|
||||
menus = {
|
||||
actors: {
|
||||
folder: 'adversaries',
|
||||
render: {
|
||||
folders: ['adversaries', 'characters', 'environments']
|
||||
}
|
||||
},
|
||||
items: {
|
||||
folder: 'equipments',
|
||||
render: {
|
||||
noFolder: true
|
||||
}
|
||||
},
|
||||
compendium: {}
|
||||
};
|
||||
|
||||
if (Object.keys(menus).includes(sectionId)) {
|
||||
const headerActions = html.querySelector('.header-actions');
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.classList.add('open-compendium-browser');
|
||||
button.innerHTML = `
|
||||
<i class="fa-solid fa-book-atlas"></i>
|
||||
${game.i18n.localize('DAGGERHEART.UI.Tooltip.compendiumBrowser')}
|
||||
`;
|
||||
button.addEventListener('click', event => {
|
||||
ui.compendiumBrowser.open(menus[sectionId]);
|
||||
});
|
||||
|
||||
headerActions.append(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue