mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-05 20:34:15 +02:00
Compare commits
19 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3527fd7959 | ||
|
|
f0a7539018 | ||
|
|
5be79f4ab8 | ||
|
|
2fc5b01f09 | ||
|
|
52b81de11f | ||
|
|
c0c9095847 | ||
|
|
5ac4fc3b9c | ||
|
|
6747be49b2 | ||
|
|
77c5cfcbb7 | ||
|
|
5dbcd94480 | ||
|
|
d98a7c951e | ||
|
|
3c36c5747d | ||
|
|
bcf274f1d0 | ||
|
|
df4a2c5d57 | ||
|
|
646ebc8bdf | ||
|
|
6448666579 | ||
|
|
d0c29ede56 | ||
|
|
98ce49b928 | ||
|
|
318d00b47d |
36 changed files with 270 additions and 94 deletions
|
|
@ -66,6 +66,10 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart
|
|||
|
||||
Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
||||
|
||||
## AI Policy
|
||||
|
||||
The Foundryborne Daggerheart system does not make use of AI (generative or otherwise) for any area of its implementation. We expect all contributors to follow this same policy when contributing with a pull request; contributions made using AI will be rejected outright.
|
||||
|
||||
## Disclaimer:
|
||||
|
||||
**Daggerheart System**
|
||||
|
|
|
|||
|
|
@ -446,3 +446,33 @@ Hooks.on('canvasTearDown', canvas => {
|
|||
Hooks.on('canvasReady', canas => {
|
||||
game.system.registeredTriggers.registerSceneTriggers(canvas.scene);
|
||||
});
|
||||
|
||||
/** Make the user to select a document type, instead of having a default doc type for them to accidentally keep */
|
||||
Hooks.on('renderDialogV2', (_dialog, html) => {
|
||||
if (!html.classList.contains('dialog')) return;
|
||||
const cls = html.classList.contains('item-create')
|
||||
? documents.DHItem.implementation
|
||||
: html.classList.contains('actor-create')
|
||||
? documents.DhpActor.implementation
|
||||
: null;
|
||||
if (!cls) return;
|
||||
|
||||
const form = html.querySelector('form');
|
||||
const submit = html.querySelector('button[type=submit]');
|
||||
const select = html.querySelector('select[name=type]');
|
||||
const nameInput = html.querySelector('input[name=name]');
|
||||
if (!form || !select || !submit || !nameInput) return;
|
||||
|
||||
nameInput.placeholder = cls.defaultName({});
|
||||
const emptyOption = document.createElement('option');
|
||||
emptyOption.value = '';
|
||||
emptyOption.selected = true;
|
||||
select.required = true;
|
||||
select.prepend(emptyOption);
|
||||
submit.addEventListener('click', event => {
|
||||
if (!form.reportValidity()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -711,9 +711,9 @@
|
|||
},
|
||||
"PendingReactionsDialog": {
|
||||
"title": "Pending Reaction Rolls Found",
|
||||
"unfinishedRolls": "Some Tokens still need to roll their Reaction Roll.",
|
||||
"confirmation": "Are you sure you want to continue ?",
|
||||
"warning": "Undone reaction rolls will be considered as failed"
|
||||
"unfinishedRolls": "Some Tokens have not finished their Reaction Rolls.",
|
||||
"warning": "Unfinished reaction rolls will be considered as failed.",
|
||||
"confirmation": "Are you sure you want to continue?"
|
||||
},
|
||||
"ReactionRoll": {
|
||||
"title": "Reaction Roll: {trait}"
|
||||
|
|
|
|||
|
|
@ -358,14 +358,14 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||
if (experienceIncreaseTagify) {
|
||||
const allExperiences = {
|
||||
...this.actor.system.experiences,
|
||||
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||
for (const key of Object.keys(level.achievements.experiences)) {
|
||||
acc[key] = level.achievements.experiences[key];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
}, {}),
|
||||
...this.actor.system.experiences
|
||||
};
|
||||
tagifyElement(
|
||||
experienceIncreaseTagify,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
switch (partId) {
|
||||
case 'domains':
|
||||
const selectedDomain = this.selected.domain ? this.settings.domains[this.selected.domain] : null;
|
||||
const selectedDomain = this.settings.domains[this.selected.domain] ?? null;
|
||||
const enrichedDescription = selectedDomain
|
||||
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description)
|
||||
: null;
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata.isInventoryItem) {
|
||||
if (this.action.parent.metadata?.isInventoryItem) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
|
|
|
|||
|
|
@ -31,6 +31,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||
|
|
@ -1045,7 +1053,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip',
|
||||
cssClass: 'bordered-tooltip dh-style',
|
||||
direction: 'DOWN'
|
||||
});
|
||||
|
||||
|
|
@ -1141,7 +1149,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip',
|
||||
cssClass: 'bordered-tooltip dh-style',
|
||||
direction: 'DOWN',
|
||||
noOffset: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,17 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
toggleStress: DhCompanionSheet.#toggleStress,
|
||||
actionRoll: DhCompanionSheet.#actionRoll,
|
||||
levelManagement: DhCompanionSheet.#levelManagement
|
||||
}
|
||||
},
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
|
|||
|
|
@ -47,11 +47,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs',
|
||||
scrollable: ['']
|
||||
},
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// projects: {
|
||||
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
||||
// scrollable: ['']
|
||||
// },
|
||||
inventory: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/party/inventory.hbs',
|
||||
scrollable: ['.tab.inventory .items-section']
|
||||
|
|
@ -62,19 +57,13 @@ export default class Party extends DHBaseActorSheet {
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [
|
||||
{ id: 'partyMembers' },
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// { id: 'projects' },
|
||||
{ id: 'inventory' },
|
||||
{ id: 'notes' }
|
||||
],
|
||||
tabs: [{ id: 'partyMembers' }, { id: 'inventory' }, { id: 'notes' }],
|
||||
initial: 'partyMembers',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
||||
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary', 'npc'];
|
||||
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||
|
||||
async _onRender(context, options) {
|
||||
|
|
|
|||
|
|
@ -189,6 +189,43 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for the base attack.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static getBaseAttackContextOptions() {
|
||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
return [
|
||||
{
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name',
|
||||
icon: 'fa-solid fa-burst',
|
||||
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
const config = action.prepareConfig(event);
|
||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(
|
||||
this.document,
|
||||
doc
|
||||
);
|
||||
config.hasRoll = false;
|
||||
return action && action.workflow.get('damage').execute(config, null, true);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Listener Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -84,19 +84,49 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the dialog used to edit the name of the currently viewed Combat encounter.
|
||||
* @this {CombatTracker}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async #onEditName() {
|
||||
const combat = this.viewed;
|
||||
if (!combat || !game.user.isGM) return null;
|
||||
const field = combat.schema.fields.name;
|
||||
const inputHTML = field.toFormGroup({}, { name: 'name', value: combat.name, autofocus: true }).outerHTML;
|
||||
const formData = await foundry.applications.api.DialogV2.input({
|
||||
window: { icon: 'fa-solid fa-tag', title: 'COMBAT.ACTIONS.EditNameTitle' },
|
||||
position: { width: 480 },
|
||||
content: inputHTML
|
||||
});
|
||||
await combat.update({ name: formData.name || '' });
|
||||
}
|
||||
|
||||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
label: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
label: 'COMBAT.ACTIONS.EditName',
|
||||
icon: 'fa-solid fa-tag',
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
onClick: () => DhCombatTracker.#onEditName.call(this)
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
label: 'COMBAT.ACTIONS.LinkToScene',
|
||||
icon: '<i class="fa-solid fa-link"></i>',
|
||||
visible: () => game.user.isGM && !this.scene,
|
||||
onClick: () => this.viewed.toggleSceneLink()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.ACTIONS.UnlinkFromScene',
|
||||
icon: '<i class="fa-solid fa-unlink"></i>',
|
||||
visible: () => game.user.isGM && !!this.scene,
|
||||
onClick: () => this.viewed.toggleSceneLink()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.End',
|
||||
icon: 'fa-solid fa-xmark',
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
onClick: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -133,7 +163,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
type: combatant.actor?.system?.type,
|
||||
img: await this._getCombatantThumbnail(combatant),
|
||||
disposition: combatant.token.disposition
|
||||
disposition: combatant.token?.disposition
|
||||
};
|
||||
|
||||
turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin(
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
minimizable: false
|
||||
},
|
||||
actions: {
|
||||
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||
editCountdowns: DhCountdowns.#editCountdowns,
|
||||
loopCountdown: DhCountdowns.#loopCountdown,
|
||||
toggleViewMode: DhCountdowns.#onToggleViewMode,
|
||||
editCountdowns: DhCountdowns.#onEditCountdowns,
|
||||
loopCountdown: DhCountdowns.#onLoopCountdown,
|
||||
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||
},
|
||||
|
|
@ -147,7 +147,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return true;
|
||||
}
|
||||
|
||||
static async #toggleViewMode() {
|
||||
static async #onToggleViewMode() {
|
||||
const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode);
|
||||
const appMode = CONFIG.DH.GENERAL.countdownAppMode;
|
||||
const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon;
|
||||
|
|
@ -158,15 +158,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async #editCountdowns() {
|
||||
static async #onEditCountdowns() {
|
||||
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||
}
|
||||
|
||||
static async #loopCountdown(_, target) {
|
||||
static async #onLoopCountdown(_, target) {
|
||||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = settings.countdowns[target.id];
|
||||
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||
const countdown = settings.countdowns[countdownId];
|
||||
|
||||
let progressMax = countdown.progress.start;
|
||||
let message = null;
|
||||
|
|
@ -185,7 +186,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
|
||||
await waitForDiceSoNice(message);
|
||||
await settings.updateSource({
|
||||
[`countdowns.${target.id}.progress`]: {
|
||||
[`countdowns.${countdownId}.progress`]: {
|
||||
current: newMax,
|
||||
start: newMax
|
||||
}
|
||||
|
|
@ -199,11 +200,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = settings.countdowns[target.id];
|
||||
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||
const countdown = settings.countdowns[countdownId];
|
||||
const newCurrent = increase
|
||||
? Math.min(countdown.progress.current + 1, countdown.progress.start)
|
||||
: Math.max(countdown.progress.current - 1, 0);
|
||||
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
||||
await settings.updateSource({ [`countdowns.${countdownId}.progress.current`]: newCurrent });
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,7 +75,12 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
||||
for (const { value, valueAlt, type } of damage.parts) {
|
||||
const usedValue = useAltDamage ? valueAlt : value;
|
||||
const str = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||
const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||
const str = damageString
|
||||
? damageString
|
||||
: game.i18n.format('DAGGERHEART.GENERAL.missingX', {
|
||||
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
||||
});
|
||||
|
||||
const icons = Array.from(type)
|
||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default class DhCountdownAction extends DHBaseAction {
|
|||
|
||||
/** @inheritDoc */
|
||||
static migrateData(source) {
|
||||
for (const countdown of source.countdown) {
|
||||
for (const countdown of Object.values(source.countdown)) {
|
||||
if (countdown.progress.max) {
|
||||
countdown.progress.startFormula = countdown.progress.max;
|
||||
countdown.progress.start = 1;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||
import { parseInlineParams } from '../../enrichers/parser.mjs';
|
||||
|
||||
export function getTierAdjustedAdversary(source, tier) {
|
||||
const currentTier = source.tier ?? 1;
|
||||
|
|
@ -60,8 +61,8 @@ export function getTierAdjustedAdversary(source, tier) {
|
|||
const descriptionFormulas = [];
|
||||
for (const withDescription of [item.system, ...Object.values(item.system.actions)]) {
|
||||
withDescription.description = withDescription.description.replace(damageRegex, (match, inner) => {
|
||||
const { value: formula } = parseInlineParams(inner);
|
||||
if (!formula || !type) return match;
|
||||
const { value: formula } = parseInlineParams(inner, { first: 'value' });
|
||||
if (!formula) return match;
|
||||
|
||||
try {
|
||||
const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import BaseDataItem from './base.mjs';
|
||||
import ItemLinkFields from '../../data/fields/itemLinkFields.mjs';
|
||||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHAncestry extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -45,6 +45,10 @@ export default class DHAncestry extends BaseDataItem {
|
|||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
// Preload all ancestry features for acquisition from the cache
|
||||
// todo: make feature acquisition async and replace feature helpers for methods
|
||||
await fromUuids(this._source.features.map(f => f.item));
|
||||
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
|
|
@ -27,6 +27,10 @@ export default class DHCommunity extends BaseDataItem {
|
|||
|
||||
/**@inheritdoc */
|
||||
async getDescriptionData() {
|
||||
// Preload all community features for acquisition from the cache
|
||||
// todo: make feature acquisition async and replace feature helpers for methods
|
||||
await fromUuids(this._source.features);
|
||||
|
||||
const baseDescription = this.description;
|
||||
const features = await getFeaturesHTMLData(this.features);
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label)
|
||||
: null;
|
||||
|
||||
// Preload all class features for acquisition from the cache
|
||||
// Preload all subclass features for acquisition from the cache
|
||||
// todo: make feature acquisition async and replace feature helpers for methods
|
||||
await fromUuids(this._source.features.map(f => f.item));
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export default class DHRoll extends Roll {
|
|||
static async buildConfigure(config = {}, message = {}) {
|
||||
config.hooks = [...this.getHooks(), ''];
|
||||
config.dialog ??= {};
|
||||
config.damageOptions ??= {};
|
||||
|
||||
for (const hook of config.hooks) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
export function updateResourcesForDualityReroll(oldDuality, newDuality, actor) {
|
||||
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||
if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return;
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ export default class DhpActor extends Actor {
|
|||
};
|
||||
}
|
||||
|
||||
static createDialog(data, createOptions, options, renderOptions) {
|
||||
options.classes = [options.classes ?? [], 'actor-create'].flat(); // handled in hook
|
||||
return super.createDialog(data, createOptions, options, renderOptions);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
|
|||
|
|
@ -183,7 +183,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
if (pendingingSaves.length) {
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') },
|
||||
content: `<p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}</p><p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}</p><p><i>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}</i></p>`
|
||||
content: `
|
||||
<p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}</p>
|
||||
<p><i>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}</i></p>
|
||||
<p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}</p>
|
||||
`
|
||||
});
|
||||
if (!confirm) return;
|
||||
}
|
||||
|
|
@ -247,8 +251,24 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
const targets = this.filterPermTargets(this.system.hitTargets),
|
||||
config = foundry.utils.deepClone(this.system);
|
||||
config.event = event;
|
||||
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
||||
else if (config.hasSave) {
|
||||
const pendingingSaves = targets.filter(t => t.saved.success === null);
|
||||
if (pendingingSaves.length) {
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
window: { title: game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.title') },
|
||||
content: `
|
||||
<p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.unfinishedRolls')}</p>
|
||||
<p><i>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.warning')}</i></p>
|
||||
<p>${game.i18n.localize('DAGGERHEART.APPLICATIONS.PendingReactionsDialog.confirmation')}</p>
|
||||
`
|
||||
});
|
||||
if (!confirm) return;
|
||||
}
|
||||
}
|
||||
|
||||
this.consumeOnSuccess();
|
||||
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ export default class DHItem extends foundry.documents.Item {
|
|||
/** @inheritdoc */
|
||||
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
||||
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
||||
dialogOptions.classes = [options.classes ?? [], 'item-create'].flat(); // handled in hook
|
||||
|
||||
if (types?.length === 0) {
|
||||
throw new Error('The array of sub-types to restrict to must not be empty.');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { AdversaryBPPerEncounter, BaseBPPerEncounter } from '../config/encounter
|
|||
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
|
||||
#wide = false;
|
||||
#bordered = false;
|
||||
#active = false;
|
||||
|
||||
async activate(element, options = {}) {
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { parseInlineParams } from './parser.mjs';
|
||||
|
||||
export default function DhDamageEnricher(match, _options) {
|
||||
const { value, type, inline } = parseInlineParams(match[1]);
|
||||
const { value, type, inline } = parseInlineParams(match[1], { first: 'value' });
|
||||
if (!value || !type) return match[0];
|
||||
return getDamageMessage(value, type, inline, match[0]);
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ export const renderDamageButton = async event => {
|
|||
{
|
||||
formula: value,
|
||||
applyTo: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
||||
type: type
|
||||
damageTypes: type
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function parseInlineParams(paramString, { first } = {}) {
|
|||
const parts = paramString.split('|').map(x => x.trim());
|
||||
const params = {};
|
||||
for (const [idx, param] of parts.entries()) {
|
||||
if (first && idx === 0) {
|
||||
if (first && idx === 0 && !param.includes(':')) {
|
||||
params[first] = param;
|
||||
} else {
|
||||
const parts = param.split(':');
|
||||
|
|
|
|||
|
|
@ -879,6 +879,7 @@ export async function fromUuids(uuids) {
|
|||
const packEmbeddedEntries = entries.filter(
|
||||
e =>
|
||||
!(e.value instanceof Document) &&
|
||||
e.parsed &&
|
||||
e.parsed.collection instanceof foundry.documents.collections.CompendiumCollection &&
|
||||
e.parsed.embedded.length > 0
|
||||
);
|
||||
|
|
@ -895,7 +896,7 @@ export async function fromUuids(uuids) {
|
|||
const pack = game.packs.get(packGroup[0].value.pack);
|
||||
if (!pack) continue;
|
||||
|
||||
const ids = packGroup.map(p => p.parsed.id);
|
||||
const ids = packGroup.map(p => p.parsed?.id).filter(id => !!id);
|
||||
const documents = await pack.getDocuments({ _id__in: ids });
|
||||
for (const p of packGroup) {
|
||||
p.value = documents.find(d => d.id === p.parsed.id) ?? p.value;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@
|
|||
}
|
||||
|
||||
.status-effects {
|
||||
// TODO: Remove when the issue https://github.com/foundryvtt/foundryvtt/issues/14410 is resolved and Foundry handles it cleanly themselves.
|
||||
grid-template-rows: min-content;
|
||||
|
||||
.effect-control-container {
|
||||
position: relative;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
.portrait {
|
||||
cursor: pointer;
|
||||
width: 275px;
|
||||
max-width: 275px;
|
||||
|
||||
img {
|
||||
height: 275px;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
border: 0;
|
||||
box-shadow: none;
|
||||
color: @color-text-primary;
|
||||
width: 300px;
|
||||
width: 18.75rem;
|
||||
pointer-events: all;
|
||||
align-self: flex-end;
|
||||
transition: 0.3s right ease-in-out;
|
||||
|
|
@ -36,7 +36,7 @@
|
|||
transition: opacity var(--ui-fade-duration);
|
||||
}
|
||||
|
||||
:not(.performance-low, .noblur) {
|
||||
&:not(.performance-low, .noblur) {
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
|
|
@ -49,8 +49,7 @@
|
|||
}
|
||||
|
||||
&.icon-only {
|
||||
width: 180px;
|
||||
min-width: 180px;
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.countdowns-header,
|
||||
|
|
@ -108,8 +107,8 @@
|
|||
gap: 16px;
|
||||
|
||||
img {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +126,7 @@
|
|||
.countdown-tool-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: var(--spacer-12);
|
||||
}
|
||||
|
||||
.progress-tag {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "daggerheart",
|
||||
"title": "Daggerheart",
|
||||
"description": "An unofficial implementation of the Daggerheart system",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.2",
|
||||
"compatibility": {
|
||||
"minimum": "14.361",
|
||||
"verified": "14.363",
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"url": "https://github.com/Foundryborne/daggerheart",
|
||||
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
|
||||
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.0/system.zip",
|
||||
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.3.2/system.zip",
|
||||
"authors": [
|
||||
{
|
||||
"name": "WBHarry"
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@
|
|||
<span class="description">
|
||||
<i>{{{description}}}</i>
|
||||
</span>
|
||||
<div class="motives-and-tactics">
|
||||
<b>{{localize 'DAGGERHEART.ACTORS.NPC.FIELDS.motives.label'}}: </b>
|
||||
{{source.system.motives}}
|
||||
</div>
|
||||
{{#if source.system.motives}}
|
||||
<div class="motives-and-tactics">
|
||||
<b>{{localize 'DAGGERHEART.ACTORS.NPC.FIELDS.motives.label'}}: </b>
|
||||
{{source.system.motives}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
{{#if member.difficulty includeZero=true}}
|
||||
<div class="evasion" data-tooltip="DAGGERHEART.GENERAL.difficulty">{{member.difficulty}}</div>
|
||||
{{/if}}
|
||||
{{#unless (eq member.type 'companion')}}
|
||||
{{#unless (or (eq member.type 'companion') (eq member.type 'npc'))}}
|
||||
<div class="threshold-section">
|
||||
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.minor"}}</h4>
|
||||
<h4 class="threshold-value">{{member.damageThresholds.major}}</h4>
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
|
||||
</h2>
|
||||
<div>
|
||||
{{#unless (or (eq member.type 'companion') (eq member.type 'adversary')) }}
|
||||
{{#unless (or (eq member.type 'companion') (eq member.type 'adversary') (eq member.type 'npc')) }}
|
||||
<div class="hope-section">
|
||||
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
|
||||
{{#times member.resources.hope.max}}
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
</header>
|
||||
<section class="body">
|
||||
<section class="resources">
|
||||
{{#unless (eq member.type 'companion') }}
|
||||
{{#unless (or (eq member.type 'companion') (eq member.type 'npc')) }}
|
||||
<div class="slot-section">
|
||||
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.HitPoints.plural">
|
||||
<span class="label">
|
||||
|
|
@ -101,25 +101,27 @@
|
|||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class="slot-section">
|
||||
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.stress">
|
||||
<span class="label">
|
||||
<i class="fa-solid fa-bolt" inert></i>
|
||||
</span>
|
||||
<span class="value">
|
||||
<span class="current">{{member.resources.stress.value}}</span>
|
||||
/
|
||||
<span class="max">{{member.resources.stress.max}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="slot-bar">
|
||||
{{#times member.resources.stress.max}}
|
||||
<span class='slot {{#if (gte member.resources.stress.value (add this 1))}}filled{{/if}}'
|
||||
data-action='toggleStress' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
|
||||
{{#unless (eq member.type 'npc')}}
|
||||
<div class="slot-section">
|
||||
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.stress">
|
||||
<span class="label">
|
||||
<i class="fa-solid fa-bolt" inert></i>
|
||||
</span>
|
||||
{{/times}}
|
||||
<span class="value">
|
||||
<span class="current">{{member.resources.stress.value}}</span>
|
||||
/
|
||||
<span class="max">{{member.resources.stress.max}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="slot-bar">
|
||||
{{#times member.resources.stress.max}}
|
||||
<span class='slot {{#if (gte member.resources.stress.value (add this 1))}}filled{{/if}}'
|
||||
data-action='toggleStress' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
|
||||
</span>
|
||||
{{/times}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
{{#if member.armorScore.max}}
|
||||
<div class="slot-section">
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@
|
|||
</nav>
|
||||
{{/if}}
|
||||
|
||||
{{!-- Encounter Name --}}
|
||||
{{#if combat.name}}
|
||||
<h2 class="encounter-name">{{ combat.name }}</h2>
|
||||
{{/if}}
|
||||
|
||||
<div class="encounter-controls {{#if hasCombat}}combat{{/if}}">
|
||||
{{!-- Combat Status --}}
|
||||
<strong class="encounter-title">
|
||||
|
|
|
|||
|
|
@ -11,18 +11,20 @@
|
|||
</header>
|
||||
<div class="countdowns-container">
|
||||
{{#each countdowns as | countdown id |}}
|
||||
<div class="countdown-container {{#if ../iconOnly}}icon-only{{/if}}">
|
||||
<div class="countdown-container {{#if ../iconOnly}}icon-only{{/if}}" data-countdown="{{id}}">
|
||||
<div class="countdown-main-container">
|
||||
<img src="{{countdown.img}}" {{#if ../iconOnly}}data-tooltip="{{countdown.name}}"{{/if}}/>
|
||||
<div class="countdown-content">
|
||||
{{#unless ../iconOnly}}<label>{{countdown.name}}</label>{{/unless}}
|
||||
{{#unless ../iconOnly}}
|
||||
<header>{{countdown.name}}</header>
|
||||
{{/unless}}
|
||||
<div class="countdown-tools">
|
||||
<div class="countdown-tool-controls">
|
||||
{{#if countdown.editable}}<a data-action="decreaseCountdown" id="{{id}}"><i class="fa-solid fa-minus"></i></a>{{/if}}
|
||||
{{#if countdown.editable}}<a data-action="decreaseCountdown"><i class="fa-solid fa-minus"></i></a>{{/if}}
|
||||
<div class="progress-tag">
|
||||
{{countdown.progress.current}}/{{countdown.progress.start}}
|
||||
</div>
|
||||
{{#if countdown.editable}}<a data-action="increaseCountdown" id="{{id}}"><i class="fa-solid fa-plus"></i></a>{{/if}}
|
||||
{{#if countdown.editable}}<a data-action="increaseCountdown"><i class="fa-solid fa-plus"></i></a>{{/if}}
|
||||
</div>
|
||||
<div class="countdown-tool-icons">
|
||||
{{#if (not ../iconOnly)}}
|
||||
|
|
@ -31,7 +33,7 @@
|
|||
{{/if}}
|
||||
{{#unless (eq countdown.progress.looping "noLooping")}}
|
||||
<span data-tooltip="{{countdown.loopTooltip}}">
|
||||
<a class="looping-container {{#if countdown.shouldLoop}}should-loop{{/if}}" {{#if countdown.loopDisabled}}disabled{{/if}} data-action="loopCountdown" id="{{id}}">
|
||||
<a class="looping-container {{#if countdown.shouldLoop}}should-loop{{/if}}" {{#if countdown.loopDisabled}}disabled{{/if}} data-action="loopCountdown">
|
||||
<i class="loop-marker fa-solid fa-repeat"></i>
|
||||
{{#if (eq countdown.progress.looping "increasing")}}
|
||||
<i class="direction-marker fa-solid fa-angles-up" data-tooltip="{{localize "DAGGERHEART.UI.Countdowns.increasingLoop"}}"></i>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue