mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 06:26:13 +01:00
Compare commits
No commits in common. "6a5c6d69086230b7ce464f9fb006977ad95c11ac" and "76c21302d2a31ec69ca6b88835d84d8d28708eaa" have entirely different histories.
6a5c6d6908
...
76c21302d2
30 changed files with 313 additions and 337 deletions
17
lang/en.json
17
lang/en.json
|
|
@ -1048,8 +1048,7 @@
|
|||
},
|
||||
"vulnerable": {
|
||||
"name": "Vulnerable",
|
||||
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again.",
|
||||
"autoAppliedByLabel": "Max Stress"
|
||||
"description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again."
|
||||
}
|
||||
},
|
||||
"CountdownType": {
|
||||
|
|
@ -1184,12 +1183,12 @@
|
|||
},
|
||||
"far": {
|
||||
"name": "Far",
|
||||
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility roll to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
|
||||
"description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.",
|
||||
"short": "Far"
|
||||
},
|
||||
"veryFar": {
|
||||
"name": "Very Far",
|
||||
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility roll to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
|
||||
"description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.",
|
||||
"short": "V. Far"
|
||||
}
|
||||
},
|
||||
|
|
@ -1312,7 +1311,6 @@
|
|||
"triggerTexts": {
|
||||
"strangePatternsContentTitle": "Matched {nr} times.",
|
||||
"strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.",
|
||||
"strangePatternsActionExplanation": "Left click to increase, right click to decrease",
|
||||
"ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?",
|
||||
"ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you."
|
||||
},
|
||||
|
|
@ -2588,10 +2586,6 @@
|
|||
"gm": { "label": "GM" },
|
||||
"players": { "label": "Players" }
|
||||
},
|
||||
"vulnerableAutomation": {
|
||||
"label": "Vulnerable Automation",
|
||||
"hint": "Automatically apply the Vulnerable condition when a actor reaches max stress"
|
||||
},
|
||||
"countdownAutomation": {
|
||||
"label": "Countdown Automation",
|
||||
"hint": "Automatically progress countdowns based on their progression settings"
|
||||
|
|
@ -2842,7 +2836,7 @@
|
|||
"title": "Domain Card"
|
||||
},
|
||||
"dualityRoll": {
|
||||
"abilityCheckTitle": "{ability} Roll"
|
||||
"abilityCheckTitle": "{ability} Check"
|
||||
},
|
||||
"effectSummary": {
|
||||
"title": "Effects Applied",
|
||||
|
|
@ -2857,7 +2851,7 @@
|
|||
"selectLeader": "Select a Leader",
|
||||
"selectMember": "Select a Member",
|
||||
"rerollTitle": "Reroll Group Roll",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} roll?",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} check?",
|
||||
"rerollTooltip": "Reroll",
|
||||
"wholePartySelected": "The whole party is selected"
|
||||
},
|
||||
|
|
@ -3024,7 +3018,6 @@
|
|||
"tokenActorsMissing": "[{names}] missing Actors",
|
||||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"knowTheTide": "Know The Tide gained a token",
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||
"cannotAlterArmorEffectChanges": "You cannot alter the changes length of an armor effect",
|
||||
"cannotAlterArmorEffectType": "You cannot alter the type of armor effect changes",
|
||||
"cannotAlterArmorEffectKey": "You cannot alter they key of armor effect changes"
|
||||
|
|
|
|||
|
|
@ -187,7 +187,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
});
|
||||
}
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -228,7 +227,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
}
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -248,8 +246,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
await this.settings.updateSource({
|
||||
[`${path}.${id}`]: _del
|
||||
});
|
||||
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,43 +4,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.changeChoices = DhActiveEffectConfig.getChangeChoices();
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style']
|
||||
};
|
||||
|
||||
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',
|
||||
templates: ['systems/daggerheart/templates/sheets/activeEffect/change.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'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get ChangeChoices for the changes autocomplete. Static for use in this class aswell as in settings-active-effect-config.mjs
|
||||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
||||
*/
|
||||
static getChangeChoices() {
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
||||
|
||||
const getAllLeaves = (root, group, parentPath = '') => {
|
||||
|
|
@ -60,7 +23,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
|
||||
return leaves;
|
||||
};
|
||||
return Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||
this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => {
|
||||
if (ignoredActorKeys.includes(key)) return acc;
|
||||
|
||||
const model = game.system.api.models.actors[key];
|
||||
|
|
@ -99,6 +62,35 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
}, []);
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style']
|
||||
};
|
||||
|
||||
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',
|
||||
templates: ['systems/daggerheart/templates/sheets/activeEffect/change.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'
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const changeChoices = this.changeChoices;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,19 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
super({});
|
||||
|
||||
this.effect = foundry.utils.deepClone(effect);
|
||||
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
|
||||
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 = {
|
||||
|
|
|
|||
|
|
@ -73,11 +73,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
return context;
|
||||
}
|
||||
|
||||
static async updateData(_event, _element, formData) {
|
||||
static async updateData(event, element, formData) {
|
||||
const data = foundry.utils.expandObject(formData.object);
|
||||
await this.updateMove({
|
||||
[`${this.movePath}`]: data
|
||||
});
|
||||
foundry.utils.mergeObject(this.move, data);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
|
@ -137,7 +135,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
}
|
||||
);
|
||||
|
||||
await this.updateMove({ [`${this.actionsPath}.${action.id}`]: action });
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${action.id}`]: action });
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -150,12 +150,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect);
|
||||
if (!updatedEffect) return;
|
||||
|
||||
await this.updateMove({
|
||||
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();
|
||||
} else {
|
||||
const action = this.move.actions.get(id);
|
||||
|
|
@ -170,13 +171,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
: existingEffectIndex === -1
|
||||
? [...currentEffects, effectData]
|
||||
: currentEffects.with(existingEffectIndex, effectData);
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: updatedEffects
|
||||
});
|
||||
}
|
||||
|
||||
await this.updateMove({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
return updatedEffects;
|
||||
}).render(true);
|
||||
|
|
@ -198,34 +199,31 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
});
|
||||
}
|
||||
}
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[this.movePath]: {
|
||||
effects: move.effects.filter(x => x.id !== id),
|
||||
actions: move.actions
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this.updateMove({ [`${this.actionsPath}.${target.dataset.id}`]: _del });
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.${target.dataset.id}`]: _del });
|
||||
}
|
||||
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async addEffect() {
|
||||
static async addEffect(_, target) {
|
||||
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||
|
||||
await this.updateMove({
|
||||
await this.settings.updateSource({
|
||||
[`${this.movePath}.effects`]: [
|
||||
...currentEffects,
|
||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||
]
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
async updateMove(update) {
|
||||
await this.settings.updateSource(update);
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static resetMoves() {}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
|||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||
import DhpActor from '../../../documents/actor.mjs';
|
||||
import DHItem from '../../../documents/item.mjs';
|
||||
|
||||
export default class Party extends DHBaseActorSheet {
|
||||
constructor(options) {
|
||||
|
|
@ -268,6 +269,15 @@ export default class Party extends DHBaseActorSheet {
|
|||
).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for Consumable and Loot.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static #getItemContextOptions() {
|
||||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
/* Filter Tracking */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -294,15 +294,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
|
||||
/* Handling transfer of inventoryItems */
|
||||
if (item.system.metadata.isInventoryItem) {
|
||||
if (!this.document.testUserPermission(game.user, 'OWNER', { exact: true })) {
|
||||
return ui.notifications.error(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.lackingItemTransferPermission', {
|
||||
user: game.user.name,
|
||||
target: this.document.name
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (item.system.metadata.isQuantifiable) {
|
||||
const actorItem = originActor.items.get(data.originId);
|
||||
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||
|
|
@ -311,6 +302,14 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
});
|
||||
|
||||
if (quantityTransfered) {
|
||||
if (quantityTransfered === actorItem.system.quantity) {
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
} else {
|
||||
await actorItem.update({
|
||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||
});
|
||||
}
|
||||
|
||||
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
||||
if (existingItem) {
|
||||
await existingItem.update({
|
||||
|
|
@ -328,18 +327,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
}
|
||||
]);
|
||||
}
|
||||
|
||||
if (quantityTransfered === actorItem.system.quantity) {
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
} else {
|
||||
await actorItem.update({
|
||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RefreshFeatures } from '../../../helpers/utils.mjs';
|
||||
import { expireActiveEffects, refreshIsAllowed } from '../../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||
|
|
@ -54,6 +54,75 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
return context;
|
||||
}
|
||||
|
||||
async getRefreshables(types) {
|
||||
const refreshedActors = {};
|
||||
for (let actor of game.actors) {
|
||||
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||
expireActiveEffects(actor, types);
|
||||
|
||||
const updates = {};
|
||||
for (let item of actor.items) {
|
||||
if (item.system.metadata?.hasResource && refreshIsAllowed(types, 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 (refreshIsAllowed(types, 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 */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -66,9 +135,30 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
|
||||
static async #refreshActors() {
|
||||
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
||||
await RefreshFeatures(refreshKeys);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,8 +206,7 @@ export const conditions = () => ({
|
|||
id: 'vulnerable',
|
||||
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
||||
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
|
||||
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description',
|
||||
autoApplyFlagId: 'auto-vulnerable'
|
||||
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description'
|
||||
},
|
||||
hidden: {
|
||||
id: 'hidden',
|
||||
|
|
|
|||
|
|
@ -467,7 +467,9 @@ export const allArmorFeatures = () => {
|
|||
};
|
||||
|
||||
export const orderedArmorFeatures = () => {
|
||||
const allFeatures = allArmorFeatures();
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.armorFeatures;
|
||||
const allFeatures = { ...armorFeatures, ...homebrewFeatures };
|
||||
const all = Object.keys(allFeatures).map(key => {
|
||||
const feature = allFeatures[key];
|
||||
return {
|
||||
|
|
@ -1424,7 +1426,9 @@ export const allWeaponFeatures = () => {
|
|||
};
|
||||
|
||||
export const orderedWeaponFeatures = () => {
|
||||
const allFeatures = allWeaponFeatures();
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.weaponFeatures;
|
||||
const allFeatures = { ...weaponFeatures, ...homebrewFeatures };
|
||||
const all = Object.keys(allFeatures).map(key => {
|
||||
const feature = allFeatures[key];
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -17,45 +17,4 @@ export default class DhCreature extends BaseDataActor {
|
|||
})
|
||||
};
|
||||
}
|
||||
|
||||
get isAutoVulnerableActive() {
|
||||
const vulnerableAppliedByOther = this.parent.effects.some(
|
||||
x => x.statuses.has('vulnerable') && !x.flags.daggerheart?.autoApplyFlagId
|
||||
);
|
||||
return !vulnerableAppliedByOther;
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, userId) {
|
||||
const allowed = await super._preUpdate(changes, options, userId);
|
||||
if (allowed === false) return;
|
||||
|
||||
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||
if (
|
||||
automationSettings.vulnerableAutomation &&
|
||||
this.parent.type !== 'companion' &&
|
||||
changes.system?.resources?.stress?.value
|
||||
) {
|
||||
const { name, description, img, autoApplyFlagId } = CONFIG.DH.GENERAL.conditions().vulnerable;
|
||||
const autoEffects = this.parent.effects.filter(
|
||||
x => x.flags.daggerheart?.autoApplyFlagId === autoApplyFlagId
|
||||
);
|
||||
if (changes.system.resources.stress.value >= this.resources.stress.max) {
|
||||
if (!autoEffects.length)
|
||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
||||
{
|
||||
name: game.i18n.localize(name),
|
||||
description: game.i18n.localize(description),
|
||||
img: img,
|
||||
statuses: ['vulnerable'],
|
||||
flags: { daggerheart: { autoApplyFlagId } }
|
||||
}
|
||||
]);
|
||||
} else if (this.resources.stress.value >= this.resources.stress.max) {
|
||||
this.parent.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
autoEffects.map(x => x.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ export default class DHArmor extends AttachableItem {
|
|||
armorFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.allArmorFeatures,
|
||||
blank: true
|
||||
}),
|
||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||
|
|
@ -66,7 +68,7 @@ export default class DHArmor extends AttachableItem {
|
|||
async getDescriptionData() {
|
||||
const baseDescription = this.description;
|
||||
const allFeatures = CONFIG.DH.ITEM.allArmorFeatures();
|
||||
const features = this.armorFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
||||
const features = this.armorFeatures.map(x => allFeatures[x.value]);
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/armor/description.hbs',
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ export default class DHWeapon extends AttachableItem {
|
|||
weaponFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true
|
||||
required: true,
|
||||
choices: CONFIG.DH.ITEM.allWeaponFeatures,
|
||||
blank: true
|
||||
}),
|
||||
effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
|
||||
|
|
@ -119,7 +121,7 @@ export default class DHWeapon extends AttachableItem {
|
|||
const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label);
|
||||
|
||||
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures();
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x);
|
||||
const features = this.weaponFeatures.map(x => allFeatures[x.value]);
|
||||
|
||||
const prefix = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sheets/items/weapon/description.hbs',
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export default class RegisteredTriggers extends Map {
|
|||
unregisterSceneEnvironmentTriggers(flagSystemData) {
|
||||
const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData);
|
||||
for (const environment of sceneData.sceneEnvironments) {
|
||||
if (!environment || environment.pack) continue;
|
||||
if (environment.pack) continue;
|
||||
this.unregisterItemTriggers(environment.system.features);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
||||
})
|
||||
}),
|
||||
vulnerableAutomation: new fields.BooleanField({
|
||||
initial: true,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.vulnerableAutomation.label'
|
||||
}),
|
||||
countdownAutomation: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: true,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
||||
import { parseRallyDice } from '../helpers/utils.mjs';
|
||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||
import DHRoll from './dhRoll.mjs';
|
||||
|
||||
|
|
@ -34,7 +33,7 @@ export default class DamageRoll extends DHRoll {
|
|||
static async buildPost(roll, config, message) {
|
||||
const chatMessage = config.source?.message
|
||||
? ui.chat.collection.get(config.source.message)
|
||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC);
|
||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||
|
|
@ -47,14 +46,9 @@ export default class DamageRoll extends DHRoll {
|
|||
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
||||
chatMessage.blind
|
||||
);
|
||||
config.mute = true;
|
||||
}
|
||||
await super.buildPost(roll, config, message);
|
||||
if (config.source?.message) {
|
||||
chatMessage.update({ 'system.damage': config.damage });
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
if (config.source?.message) chatMessage.update({ 'system.damage': config.damage });
|
||||
}
|
||||
|
||||
static unifyDamageRoll(rolls) {
|
||||
|
|
@ -198,7 +192,7 @@ export default class DamageRoll extends DHRoll {
|
|||
// Bardic Rally
|
||||
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||
if (change) a.push({ value: c.id, label: change.value });
|
||||
return a;
|
||||
}, []);
|
||||
if (rallyChoices.length) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ export default class DualityRoll extends D20Roll {
|
|||
setRallyChoices() {
|
||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||
if (change) a.push({ value: c.id, label: change.value });
|
||||
return a;
|
||||
}, []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -922,23 +922,10 @@ export default class DhpActor extends Actor {
|
|||
|
||||
/** Get active effects */
|
||||
getActiveEffects() {
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||
const autoVulnerableActive = this.system.isAutoVulnerableActive;
|
||||
return this.effects
|
||||
.filter(x => !x.disabled)
|
||||
.reduce((acc, effect) => {
|
||||
/* Could be generalized if needed. Currently just related to Vulnerable */
|
||||
const isAutoVulnerableEffect =
|
||||
effect.flags.daggerheart?.autoApplyFlagId === conditions.vulnerable.autoApplyFlagId;
|
||||
if (isAutoVulnerableEffect) {
|
||||
if (!autoVulnerableActive) return acc;
|
||||
|
||||
effect.appliedBy = game.i18n.localize('DAGGERHEART.CONFIG.Condition.vulnerable.autoAppliedByLabel');
|
||||
effect.isLockedCondition = true;
|
||||
effect.condition = 'vulnerable';
|
||||
}
|
||||
|
||||
acc.push(effect);
|
||||
|
||||
const currentStatusActiveEffects = acc.filter(
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
|
|||
}),
|
||||
maxTags: typeof maxTags === 'function' ? maxTags() : maxTags,
|
||||
dropdown: {
|
||||
searchKeys: ['value', 'name'],
|
||||
mapValueTo: 'name',
|
||||
searchKeys: ['value'],
|
||||
enabled: 0,
|
||||
maxItems: 100,
|
||||
closeOnSelect: true,
|
||||
|
|
@ -483,7 +483,7 @@ export function refreshIsAllowed(allowedTypes, typeToCheck) {
|
|||
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
||||
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
||||
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
||||
return allowedTypes.includes?.(typeToCheck) ?? allowedTypes.has(typeToCheck);
|
||||
return allowedTypes.includes(typeToCheck);
|
||||
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
||||
return allowedTypes.some(
|
||||
x =>
|
||||
|
|
@ -606,123 +606,3 @@ export function calculateExpectedValue(formulaOrTerms) {
|
|||
: [formulaOrTerms];
|
||||
return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0);
|
||||
}
|
||||
|
||||
export function parseRallyDice(value, effect) {
|
||||
const legacyStartsWithPrefix = value.toLowerCase().startsWith('d');
|
||||
const workingValue = legacyStartsWithPrefix ? value.slice(1) : value;
|
||||
const dataParsedValue = itemAbleRollParse(workingValue, effect.parent);
|
||||
|
||||
return `d${game.system.api.documents.DhActiveEffect.effectSafeEval(dataParsedValue)}`;
|
||||
}
|
||||
/**
|
||||
* Refreshes character and/or adversary resources.
|
||||
* @param { string[] } refreshTypes Which type of features to refresh using IDs from CONFIG.DH.GENERAL.refreshTypes
|
||||
* @param { string[] = ['character', 'adversary'] } actorTypes Which actor types should refresh their features. Defaults to character and adversary.
|
||||
* @param { boolean = true } sendRefreshMessage If a chat message should be created detailing the refresh
|
||||
* @return { Actor[] } The actors that had their features refreshed
|
||||
*/
|
||||
export async function RefreshFeatures(
|
||||
refreshTypes = [],
|
||||
actorTypes = ['character', 'adversary'],
|
||||
sendNotificationMessage = true,
|
||||
sendRefreshMessage = true
|
||||
) {
|
||||
const refreshedActors = {};
|
||||
for (let actor of game.actors) {
|
||||
if (actorTypes.includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||
expireActiveEffects(actor, refreshTypes);
|
||||
|
||||
const updates = {};
|
||||
for (let item of actor.items) {
|
||||
if (
|
||||
item.system.metadata?.hasResource &&
|
||||
refreshIsAllowed(refreshTypes, 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
|
||||
: game.system.api.documents.DhActiveEffect.effectSafeEval(
|
||||
Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||
)
|
||||
};
|
||||
}
|
||||
if (item.system.metadata?.hasActions) {
|
||||
const usedTypes = new Set();
|
||||
const actions = item.system.actions.filter(action => {
|
||||
if (refreshIsAllowed(refreshTypes, action.uses.recovery)) {
|
||||
usedTypes.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(
|
||||
...usedTypes.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const types = refreshTypes.map(x => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[x].label)).join(', ');
|
||||
|
||||
if (sendNotificationMessage) {
|
||||
ui.notifications.info(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
|
||||
types: `[${types}]`
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (sendRefreshMessage) {
|
||||
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);
|
||||
}
|
||||
|
||||
return refreshedActors;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@
|
|||
{
|
||||
"type": "class",
|
||||
"item": "Compendium.daggerheart.classes.Item.PydiMnNCKpd44SGS"
|
||||
},
|
||||
{
|
||||
"type": "class",
|
||||
"item": "Compendium.daggerheart.classes.Item.TVeEyqmPPiRa2r3i"
|
||||
}
|
||||
],
|
||||
"subclasses": [
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
{
|
||||
"key": "system.bonuses.rally",
|
||||
"mode": 2,
|
||||
"value": "6 + min((floor(@system.levelData.level.current / 5)*2), 2)",
|
||||
"value": "d6",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"folder": "C9y59fIkq50d3SyD",
|
||||
"name": "Rally (Level 5)",
|
||||
"type": "feature",
|
||||
"img": "icons/tools/instruments/drum-hand-tan.webp",
|
||||
"system": {
|
||||
"description": "<p>Once per session, describe how you rally the party and give yourself and each of your allies a Rally Die. At level 1, your Rally Die is a d6. A PC can spend their Rally Die to roll it, adding the result to their action roll, reaction roll, damage roll, or to clear a number of Stress equal to the result. At the end of each session, clear all unspent Rally Dice. At level 5, your Rally Die increases to a d8.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"Z1KWFrpXOqZWuZD1": {
|
||||
"type": "effect",
|
||||
"_id": "Z1KWFrpXOqZWuZD1",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "1",
|
||||
"recovery": "session"
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"_id": "8CFxYJV8zE6Wabwj",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Rally your Allies",
|
||||
"img": "icons/tools/instruments/drum-hand-tan.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
"originId": null,
|
||||
"attribution": {
|
||||
"source": "Daggerheart SRD",
|
||||
"page": 9,
|
||||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"name": "Rally (Level 5)",
|
||||
"img": "icons/tools/instruments/drum-hand-tan.webp",
|
||||
"origin": "Compendium.daggerheart.classes.Item.oxv0m8AFUQVFKtZ4",
|
||||
"transfer": false,
|
||||
"_id": "8CFxYJV8zE6Wabwj",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.bonuses.rally",
|
||||
"mode": 2,
|
||||
"value": "d8",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"_key": "!items.effects!TVeEyqmPPiRa2r3i.8CFxYJV8zE6Wabwj"
|
||||
}
|
||||
],
|
||||
"flags": {},
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
"LgnbNMLaxandgMQq": 3
|
||||
},
|
||||
"_id": "TVeEyqmPPiRa2r3i",
|
||||
"sort": 300000,
|
||||
"_key": "!items!TVeEyqmPPiRa2r3i"
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@
|
|||
{
|
||||
"trigger": "dualityRoll",
|
||||
"triggeringActorType": "self",
|
||||
"command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n<div>${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
|
||||
"command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n <div><div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}</div>\n <div>${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}</div>\n <div class=\"flexrow\" style=\"gap: 8px;\">\n <button type=\"button\" id=\"hopeButton\">\n <i class=\"fa-solid fa-hands-holding\"></i>\n <label>0</label>\n </button>\n <button type=\"button\" id=\"stressButton\">\n <i class=\"fa-solid fa-bolt-lightning\"></i>\n <label>0</label>\n </button>\n </div>\n</div>`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
"difficulty": null,
|
||||
"damageMod": "none"
|
||||
},
|
||||
"name": "Agility Roll",
|
||||
"name": "Agility Check",
|
||||
"img": "icons/skills/melee/sword-engraved-glow-purple.webp",
|
||||
"range": "close"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,9 +103,10 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: 8px;
|
||||
|
||||
a {
|
||||
width: 20px;
|
||||
width: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
@ -274,10 +275,8 @@
|
|||
grid-area: controls;
|
||||
align-self: start;
|
||||
padding-top: 0.3125rem;
|
||||
gap: 4px;
|
||||
margin-bottom: -1px;
|
||||
a {
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
> .item-labels {
|
||||
align-self: start;
|
||||
|
|
@ -335,27 +334,6 @@
|
|||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.recall-cost {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
width: 1.75em;
|
||||
height: 1.75em;
|
||||
|
||||
align-items: center;
|
||||
background: @dark-blue;
|
||||
border-radius: 50%;
|
||||
border: 1px solid @golden;
|
||||
color: @golden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 0.1em; // compensate for font
|
||||
|
||||
i {
|
||||
font-size: 0.68em;
|
||||
}
|
||||
}
|
||||
|
||||
.card-label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
|||
|
|
@ -183,11 +183,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.domain-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.level-details {
|
||||
align-self: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@
|
|||
.daggerheart,
|
||||
#chat-notifications {
|
||||
.chat-message {
|
||||
--text-color: light-dark(@dark-blue, @golden);
|
||||
--text-color: @golden;
|
||||
--bg-color: @golden-40;
|
||||
|
||||
[data-use-perm='false'] {
|
||||
|
|
@ -233,7 +233,7 @@
|
|||
font-family: @font-subtitle;
|
||||
font-size: var(--font-size-18);
|
||||
font-weight: bold;
|
||||
color: var(--text-color);
|
||||
color: light-dark(@dark-blue, var(--text-color));
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
{{formGroup settingFields.schema.fields.summaryMessages.fields.effects value=settingFields._source.summaryMessages.effects localize=true}}
|
||||
</div>
|
||||
|
||||
{{formGroup settingFields.schema.fields.vulnerableAutomation value=settingFields._source.vulnerableAutomation localize=true}}
|
||||
{{formGroup settingFields.schema.fields.autoExpireActiveEffects value=settingFields._source.autoExpireActiveEffects localize=true}}
|
||||
{{formGroup settingFields.schema.fields.countdownAutomation value=settingFields._source.countdownAutomation localize=true}}
|
||||
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
<li class="card-item" data-item-uuid="{{item.uuid}}" data-type="domainCard">
|
||||
<img src="{{item.img}}" data-action="useItem" class="card-img" />
|
||||
<span class="item-icon recall-cost">
|
||||
<span class="recall-value">{{item.system.recallCost}}</span>
|
||||
<i class="fa-solid fa-bolt"></i>
|
||||
</span>
|
||||
<div class="card-label">
|
||||
<div
|
||||
class="menu {{#if item.system.resource}}resource-menu{{/if}} {{#if (eq item.system.resource.type 'diceValue')}}dice-menu{{/if}}">
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ Parameters:
|
|||
{{else if (eq type 'armor')}}
|
||||
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
|
||||
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
|
||||
<i class="fa-solid fa-fw fa-shield"></i>
|
||||
<i class="fa-solid fa-shield"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{#if (eq type 'domainCard')}}
|
||||
|
|
@ -125,7 +125,7 @@ Parameters:
|
|||
{{/if}}
|
||||
{{#if (hasProperty item "toChat")}}
|
||||
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
|
||||
<i class="fa-regular fa-fw fa-message"></i>
|
||||
<i class="fa-regular fa-message"></i>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
|
|
@ -138,7 +138,7 @@ Parameters:
|
|||
{{/unless}}
|
||||
{{#unless hideContextMenu}}
|
||||
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
|
||||
<i class="fa-solid fa-fw fa-ellipsis-vertical"></i>
|
||||
<i class="fa-solid fa-ellipsis-vertical"></i>
|
||||
</a>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue