mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-03-07 06:26:13 +01:00
Compare commits
7 commits
f004f575a5
...
0ace9f2b8e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ace9f2b8e | ||
|
|
8727cb0dca | ||
|
|
f1f5102af1 | ||
|
|
a4f8c67707 | ||
|
|
83c3da0130 | ||
|
|
92d8c2ca18 | ||
|
|
17aa0680d2 |
15 changed files with 218 additions and 218 deletions
|
|
@ -1031,7 +1031,8 @@
|
||||||
},
|
},
|
||||||
"vulnerable": {
|
"vulnerable": {
|
||||||
"name": "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."
|
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CountdownType": {
|
"CountdownType": {
|
||||||
|
|
@ -2556,6 +2557,10 @@
|
||||||
"gm": { "label": "GM" },
|
"gm": { "label": "GM" },
|
||||||
"players": { "label": "Players" }
|
"players": { "label": "Players" }
|
||||||
},
|
},
|
||||||
|
"vulnerableAutomation": {
|
||||||
|
"label": "Vulnerable Automation",
|
||||||
|
"hint": "Automatically apply the Vulnerable condition when a actor reaches max stress"
|
||||||
|
},
|
||||||
"countdownAutomation": {
|
"countdownAutomation": {
|
||||||
"label": "Countdown Automation",
|
"label": "Countdown Automation",
|
||||||
"hint": "Automatically progress countdowns based on their progression settings"
|
"hint": "Automatically progress countdowns based on their progression settings"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { refreshIsAllowed } from '../../../helpers/utils.mjs';
|
import { RefreshFeatures } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||||
|
|
@ -54,73 +54,6 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
return context;
|
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 && 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 */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -133,30 +66,9 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
|
|
||||||
static async #refreshActors() {
|
static async #refreshActors() {
|
||||||
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
|
||||||
await this.getRefreshables(refreshKeys);
|
await RefreshFeatures(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();
|
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();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,8 @@ export const conditions = () => ({
|
||||||
id: 'vulnerable',
|
id: 'vulnerable',
|
||||||
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name',
|
||||||
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
|
img: 'icons/magic/control/silhouette-fall-slip-prone.webp',
|
||||||
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description'
|
description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description',
|
||||||
|
autoApplyFlagId: 'auto-vulnerable'
|
||||||
},
|
},
|
||||||
hidden: {
|
hidden: {
|
||||||
id: 'hidden',
|
id: 'hidden',
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,45 @@ 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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,25 +6,27 @@ export default class IterableTypedObjectField extends foundry.data.fields.TypedO
|
||||||
|
|
||||||
#elementClass;
|
#elementClass;
|
||||||
|
|
||||||
initialize(value) {
|
/** Initializes an object with an iterator. This modifies the prototype instead of */
|
||||||
return new IterableObject(value, this.#elementClass);
|
initialize(values) {
|
||||||
|
const object = Object.create(IterableObjectPrototype);
|
||||||
|
for (const [key, value] of Object.entries(values)) {
|
||||||
|
object[key] = new this.#elementClass(value);
|
||||||
|
}
|
||||||
|
return object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IterableObject {
|
/**
|
||||||
constructor(values, elementClass) {
|
* The prototype of an iterable object.
|
||||||
for (const [key, value] of Object.entries(values)) {
|
* This allows the functionality of a class but also allows foundry.utils.getType() to return "Object" instead of "Unknown".
|
||||||
this[key] = new elementClass(value);
|
*/
|
||||||
}
|
const IterableObjectPrototype = {
|
||||||
}
|
[Symbol.iterator]: function*() {
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
for (const value of Object.values(this)) {
|
for (const value of Object.values(this)) {
|
||||||
yield value;
|
yield value;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
map: function (func) {
|
||||||
map(func) {
|
|
||||||
return Array.from(this, func);
|
return Array.from(this, func);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
@ -18,6 +18,10 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
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({
|
countdownAutomation: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
initial: true,
|
initial: true,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
||||||
|
import { parseRallyDice } from '../helpers/utils.mjs';
|
||||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||||
import DHRoll from './dhRoll.mjs';
|
import DHRoll from './dhRoll.mjs';
|
||||||
|
|
||||||
|
|
@ -33,7 +34,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
const chatMessage = config.source?.message
|
const chatMessage = config.source?.message
|
||||||
? ui.chat.collection.get(config.source.message)
|
? ui.chat.collection.get(config.source.message)
|
||||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
|
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC);
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||||
|
|
@ -46,9 +47,14 @@ export default class DamageRoll extends DHRoll {
|
||||||
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
||||||
chatMessage.blind
|
chatMessage.blind
|
||||||
);
|
);
|
||||||
|
config.mute = true;
|
||||||
}
|
}
|
||||||
await super.buildPost(roll, config, message);
|
await super.buildPost(roll, config, message);
|
||||||
if (config.source?.message) chatMessage.update({ 'system.damage': config.damage });
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unifyDamageRoll(rolls) {
|
static unifyDamageRoll(rolls) {
|
||||||
|
|
@ -192,7 +198,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
// Bardic Rally
|
// Bardic Rally
|
||||||
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||||
if (change) a.push({ value: c.id, label: change.value });
|
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
if (rallyChoices.length) {
|
if (rallyChoices.length) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||||
import D20Roll from './d20Roll.mjs';
|
import D20Roll from './d20Roll.mjs';
|
||||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||||
|
|
||||||
|
|
@ -68,7 +68,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
setRallyChoices() {
|
setRallyChoices() {
|
||||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||||
if (change) a.push({ value: c.id, label: change.value });
|
if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) });
|
||||||
return a;
|
return a;
|
||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -934,10 +934,23 @@ export default class DhpActor extends Actor {
|
||||||
|
|
||||||
/** Get active effects */
|
/** Get active effects */
|
||||||
getActiveEffects() {
|
getActiveEffects() {
|
||||||
|
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||||
|
const autoVulnerableActive = this.system.isAutoVulnerableActive;
|
||||||
return this.effects
|
return this.effects
|
||||||
.filter(x => !x.disabled)
|
.filter(x => !x.disabled)
|
||||||
.reduce((acc, effect) => {
|
.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);
|
acc.push(effect);
|
||||||
|
|
||||||
const currentStatusActiveEffects = acc.filter(
|
const currentStatusActiveEffects = acc.filter(
|
||||||
|
|
|
||||||
|
|
@ -472,7 +472,7 @@ export function refreshIsAllowed(allowedTypes, typeToCheck) {
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
||||||
return allowedTypes.includes(typeToCheck);
|
return allowedTypes.includes?.(typeToCheck) ?? allowedTypes.has(typeToCheck);
|
||||||
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
||||||
return allowedTypes.some(
|
return allowedTypes.some(
|
||||||
x =>
|
x =>
|
||||||
|
|
@ -558,6 +558,124 @@ export function calculateExpectedValue(formulaOrTerms) {
|
||||||
return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0);
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export function getUnusedDamageTypes(parts) {
|
export function getUnusedDamageTypes(parts) {
|
||||||
const usedKeys = Object.keys(parts);
|
const usedKeys = Object.keys(parts);
|
||||||
return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => {
|
return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => {
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,6 @@
|
||||||
{
|
{
|
||||||
"type": "class",
|
"type": "class",
|
||||||
"item": "Compendium.daggerheart.classes.Item.PydiMnNCKpd44SGS"
|
"item": "Compendium.daggerheart.classes.Item.PydiMnNCKpd44SGS"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "class",
|
|
||||||
"item": "Compendium.daggerheart.classes.Item.TVeEyqmPPiRa2r3i"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"subclasses": [
|
"subclasses": [
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@
|
||||||
{
|
{
|
||||||
"key": "system.bonuses.rally",
|
"key": "system.bonuses.rally",
|
||||||
"mode": 2,
|
"mode": 2,
|
||||||
"value": "d6",
|
"value": "6 + min((floor(@system.levelData.level.current / 5)*2), 2)",
|
||||||
"priority": null
|
"priority": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "daggerheart",
|
"id": "daggerheart",
|
||||||
"title": "Daggerheart",
|
"title": "Daggerheart",
|
||||||
"description": "An unofficial implementation of the Daggerheart system",
|
"description": "An unofficial implementation of the Daggerheart system",
|
||||||
"version": "1.7.3",
|
"version": "1.8.0",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13.346",
|
"minimum": "13.346",
|
||||||
"verified": "13.351",
|
"verified": "13.351",
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
{{formGroup settingFields.schema.fields.summaryMessages.fields.effects value=settingFields._source.summaryMessages.effects localize=true}}
|
{{formGroup settingFields.schema.fields.summaryMessages.fields.effects value=settingFields._source.summaryMessages.effects localize=true}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{formGroup settingFields.schema.fields.vulnerableAutomation value=settingFields._source.vulnerableAutomation localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.countdownAutomation value=settingFields._source.countdownAutomation localize=true}}
|
{{formGroup settingFields.schema.fields.countdownAutomation value=settingFields._source.countdownAutomation localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
|
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
|
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue