Compare commits

...

21 commits
2.3.0 ... main

Author SHA1 Message Date
Carlos Fernandez
a4428fd5be
Replace prettier with stylistic, improve types, and add no-undef rule (#1975)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-05 15:53:15 -04:00
WBHarry
6312a171e2
[Housekeeping] Styles Index:ification (#1977) 2026-06-05 15:36:07 -04:00
WBHarry
3527fd7959 Raised version
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-05 12:31:30 +02:00
Carlos Fernandez
f0a7539018
Update README.md (#1976) 2026-06-05 12:25:44 +02:00
Carlos Fernandez
5be79f4ab8
Fix several issues with inline damage (#1973)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-05 11:33:20 +02:00
Carlos Fernandez
2fc5b01f09
Fix rerolling when hope/fear automation is enabled (#1972) 2026-06-05 11:31:01 +02:00
WBHarry
52b81de11f
Fixed so that the saved data for an experience that is in the character data is used over that in the levelup data if available (#1971)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-04 18:30:41 -04:00
Carlos Fernandez
c0c9095847
[Fix] Preload ancestry and community features in description (#1967)
* Preload ancestry and community features in description

* Corrected comments

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
2026-06-04 20:08:40 +02:00
Carlos Fernandez
5ac4fc3b9c
[Fix] visual quirk with blur in unfocused countdown (#1970)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
* Fix visual quirk with blur in unfocused countdown

* Snuck in fixes and refactors
2026-06-04 11:42:17 +02:00
Carlos Fernandez
6747be49b2
Allow removing empty string domains (#1968) 2026-06-04 11:15:41 +02:00
WBHarry
77c5cfcbb7 Fixed so that actions on homebrew downtime/items don't crash due to note having metadata
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-03 21:46:23 +02:00
WBHarry
5dbcd94480 Raised version
Some checks failed
Project CI / build (24.x) (push) Has been cancelled
2026-06-01 22:22:50 +02:00
WBHarry
d98a7c951e
[Fix] Tooltip Color Scope (#1964)
* Added DH style to tooltips

* Setting dh-style for ResourceManagementTooltip and ArmorManagementTooltip
2026-06-01 22:20:06 +02:00
WBHarry
3c36c5747d
[Fix] Base Attack Context Menu (#1961)
* Fixed Adversary standard attack context menu

* Fixed Character base attack context menu

* Fixed Companion base attack context menu
2026-06-01 22:02:42 +02:00
WBHarry
bcf274f1d0
[Fix] ChatMessage Saves Pending Confirmation (#1963) 2026-06-01 15:59:44 -04:00
WBHarry
df4a2c5d57
Fixed Countdown.migrationData assuming an array when it's actually an object (#1962) 2026-06-01 15:53:28 -04:00
WBHarry
646ebc8bdf
Added a temp fix for status effect rows (#1965) 2026-06-01 15:52:27 -04:00
WBHarry
6448666579 Updated combat contextmenu 2026-06-01 19:47:06 +02:00
WBHarry
d0c29ede56
Fixed so the combat tracker doesn't error out if a combatant has no attached token (#1960)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-06-01 06:24:00 -04:00
Carlos Fernandez
98ce49b928
Avoid default type on name and item create dialogs (#1958) 2026-06-01 11:06:24 +02:00
WBHarry
318d00b47d
Add NPC Improvements 2026-06-01 04:24:44 -04:00
136 changed files with 1337 additions and 887 deletions

View file

@ -1,5 +1,6 @@
[*]
indent_size = 4
indent_style = spaces
end_of_line = lf
[*.yml]
indent_size = 2

View file

@ -1,13 +0,0 @@
{
"trailingComma": "none",
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 120,
"endOfLine": "lf",
"bracketSameLine": true
}

View file

@ -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**

24
daggerheart.d.ts vendored
View file

@ -1,8 +1,11 @@
import '@client/global.mjs';
import '@common/global.mjs';
import '@common/primitives/global.mjs';
import Canvas from '@client/canvas/board.mjs';
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
// This declare global hopefully fixes that
// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list
declare global {
/**
* A simple event framework used throughout Foundry Virtual Tabletop.
@ -12,9 +15,28 @@ declare global {
class Hooks extends foundry.helpers.Hooks {}
const fromUuid = foundry.utils.fromUuid;
const fromUuidSync = foundry.utils.fromUuidSync;
/**
* A representation of a color in hexadecimal format.
* This class provides methods for transformations and manipulations of colors.
*/
class Color extends foundry.utils.Color {}
/**
* The singleton game canvas
*/
const canvas: Canvas;
const ActiveEffect: foundry.documents.ActiveEffect;
const Actor: foundry.documents.Actor;
const BaseScene: foundry.documents.BaseScene;
const ChatMessage: foundry.documents.ChatMessage;
const Combat: foundry.documents.Combat;
const Combatant: foundry.documents.Combatant;
const Item: foundry.documents.Item;
const Macro: foundry.documents.Macro;
const Scene: foundry.documents.Scene;
const TokenDocument: foundry.documents.TokenDocument;
const Collection: foundry.utils.Collection;
const FormDataExtended: foundry.applications.ux.FormDataExtended;
const TextEditor: foundry.applications.ux.TextEditor;
}

View file

@ -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();
}
});
});

View file

@ -1,14 +1,101 @@
import globals from 'globals';
import { defineConfig } from 'eslint/config';
import prettier from 'eslint-plugin-prettier';
import { defineConfig, globalIgnores } from 'eslint/config';
import tseslint from 'typescript-eslint';
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
/** @type {Partial<RulesConfig>} */
export const stylisticRules = {
'@stylistic/indent': [
'error',
4,
{
SwitchCase: 1
}
],
'@stylistic/max-len': ['error', {
code: 120,
ignoreComments: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
ignoreRegExpLiterals: true
}],
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/arrow-parens': ['error', 'as-needed'],
'@stylistic/quote-props': ['error', 'as-needed'],
'@stylistic/array-bracket-newline': ['error', 'consistent'],
'@stylistic/key-spacing': 'error',
'@stylistic/comma-dangle': ['error', 'never'],
'@stylistic/space-in-parens': ['error', 'never'],
'@stylistic/space-infix-ops': 2,
'@stylistic/keyword-spacing': 2,
'@stylistic/semi-spacing': 2,
'@stylistic/no-multi-spaces': 2,
'@stylistic/no-extra-semi': 2,
'@stylistic/no-whitespace-before-property': 2,
'@stylistic/space-unary-ops': 2
};
export default defineConfig([
{ files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } },
{ plugins: { prettier } },
globalIgnores(['foundry/**/*', 'build/**/*']),
{
files: ['gulpfile.js', 'postcss.config.js'],
languageOptions: { globals: globals.node }
},
{
files: ['**/*.{js,mjs,cjs}'],
plugins: {
'@stylistic': stylistic
},
languageOptions: {
globals: {
...globals.browser,
CONFIG: 'readonly',
CONST: 'readonly',
// Global classes
Color: 'readonly',
Handlebars: 'readonly',
Hooks: 'readonly',
PIXI: 'readonly',
ProseMirror: 'readonly',
Roll: 'readonly',
// global namespaces
canvas: 'readonly',
foundry: 'readonly',
game: 'readonly',
ui: 'readonly',
// global functions
fromUuid: 'readonly',
fromUuidSync: 'readonly',
getDocumentClass: 'readonly',
_del: 'readonly',
_replace: 'readonly',
_loc: 'readonly',
// Documents
ActiveEffect: 'readonly',
Actor: 'readonly',
BaseScene: 'readonly',
ChatMessage: 'readonly',
Combat: 'readonly',
Combatant: 'readonly',
Item: 'readonly',
Macro: 'readonly',
Scene: 'readonly',
TokenDocument: 'readonly',
// Other
Collection: 'readonly',
FormDataExtended: 'readonly',
TextEditor: 'readonly'
}
},
rules: {
'prettier/prettier': 'error'
'no-undef': 'error',
// 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
...stylisticRules
}
},
{
files: ['**/*.ts'],
extends: [js.configs.recommended, tseslint.configs.recommended]
}
]);

View file

@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "ES6",
"target": "ES6",
"module": "es2022",
"target": "es2022",
"paths": {
"@client/*": ["./foundry/client/*"],
"@common/*": ["./foundry/common/*"]

View file

@ -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}"

View file

@ -154,8 +154,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
v.active = this.tabGroups[v.group]
? this.tabGroups[v.group] === v.id
: this.tabGroups.primary !== 'equipment'
? v.active
: false;
? v.active
: false;
v.cssClass = v.active ? 'active' : '';
switch (v.id) {
@ -211,9 +211,9 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
context.suggestedTraits = this.setup.class.system
? Object.keys(this.setup.class.system.characterGuide.suggestedTraits).map(traitKey => {
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
})
const trait = this.setup.class.system.characterGuide.suggestedTraits[traitKey];
return `${game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${traitKey}.short`)} ${trait > 0 ? `+${trait}` : trait}`;
})
: [];
context.traits = {
values: Object.keys(this.setup.traits).map(traitKey => {
@ -450,7 +450,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
if (equipment.includes(type))
presets.filter = {
'system.tier': { key: 'system.tier', value: 1 },
'type': { key: 'type', value: type }
type: { key: 'type', value: type }
};
ui.compendiumBrowser.open(presets);

View file

@ -196,14 +196,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
: [
...this.config.costs,
{
extKey: button.dataset.key,
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
value: 1,
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
}
];
...this.config.costs,
{
extKey: button.dataset.key,
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
value: 1,
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
}
];
this.render();
}
@ -213,8 +213,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.actionType = this.reactionOverride
? 'reaction'
: this.config.actionType === 'reaction'
? 'action'
: this.config.actionType;
? 'action'
: this.config.actionType;
this.render();
}
}

View file

@ -138,13 +138,13 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
const stressReductionStress = this.availableStressReductions
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
: 0;
const stress = this.actor.system.resources.stress;
context.stress =
selectedStressMarks.length > 0 || this.availableStressReductions
? {
value:
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
max: this.actor.system.resources.stress.max
}
value: stress.value + selectedStressMarks.length + stressReductionStress,
max: stress.max
}
: null;
context.maxArmorUsed = maxArmorUsed;

View file

@ -259,10 +259,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
const resetValue = increasing
? 0
: feature.system.resource.max
? new Roll(
? new Roll(
Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData())
).evaluateSync().total
: 0;
: 0;
await feature.update({ 'system.resource.value': resetValue });
}

View file

@ -167,8 +167,8 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
partContext.groupRoll = {
totalLabel: leader?.rollData
? game.i18n.format('DAGGERHEART.GENERAL.withThing', {
thing: leader.roll.totalLabel
})
thing: leader.roll.totalLabel
})
: null,
totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear',
total: leaderTotal + modifierTotal,

View file

@ -653,8 +653,8 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
const baseSecondaryRoll = selectedRoll
? memberValues.find(x => !x.selected)
: memberValues.length > 1
? memberValues[1]
: null;
? memberValues[1]
: null;
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;

View file

@ -50,11 +50,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
).showGenericStatusEffects;
context.genericStatusEffects = useGeneric
? Object.keys(context.statusEffects).reduce((acc, key) => {
const effect = context.statusEffects[key];
if (!effect.systemEffect) acc[key] = effect;
const effect = context.statusEffects[key];
if (!effect.systemEffect) acc[key] = effect;
return acc;
}, {})
return acc;
}, {})
: null;
context.hasCompanion = this.actor.system.companion;
@ -68,11 +68,11 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
const warning =
tokensWithoutActors.length === 1
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
name: tokensWithoutActors[0].name
})
name: tokensWithoutActors[0].name
})
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
names: tokensWithoutActors.map(x => x.name).join(', ')
});
names: tokensWithoutActors.map(x => x.name).join(', ')
});
const tokens = canvas.tokens.controlled
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
@ -174,8 +174,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
nonZeroIndex === sideMiddle
? 0
: nonZeroIndex < sideMiddle
? -nonZeroIndex
: nonZeroIndex - sideMiddle;
? -nonZeroIndex
: nonZeroIndex - sideMiddle;
return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient };
} else if (index < side + inbetween) {
const inbetweenIndex = nonZeroIndex - side;
@ -183,8 +183,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
inbetweenIndex === inbetweenMiddle
? 0
: inbetweenIndex < inbetweenMiddle
? -inbetweenIndex
: inbetweenIndex - inbetweenMiddle;
? -inbetweenIndex
: inbetweenIndex - inbetweenMiddle;
return { x: actorX + sizeX * distanceCoefficient, y: actorY + sizeY * distance };
} else if (index < 2 * side + inbetween) {
const sideIndex = nonZeroIndex - side - inbetween;
@ -192,8 +192,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
sideIndex === sideMiddle
? 0
: sideIndex < sideMiddle
? sideIndex
: -(sideIndex - sideMiddle);
? sideIndex
: -(sideIndex - sideMiddle);
return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient };
} else {
const inbetweenIndex = nonZeroIndex - 2 * side - inbetween;
@ -201,8 +201,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
inbetweenIndex === inbetweenMiddle
? 0
: inbetweenIndex < inbetweenMiddle
? inbetweenIndex
: -(inbetweenIndex - inbetweenMiddle);
? inbetweenIndex
: -(inbetweenIndex - inbetweenMiddle);
return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance };
}
})

View file

@ -210,9 +210,9 @@ export default class DhCharacterLevelUp extends LevelUpBase {
achievementExperiences = level.achievements.experiences
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
if (experience.name) acc.push(experience);
return acc;
}, [])
if (experience.name) acc.push(experience);
return acc;
}, [])
: [];
}
@ -315,15 +315,15 @@ export default class DhCharacterLevelUp extends LevelUpBase {
: null;
advancement[choiceKey] = multiclassItem
? {
...multiclassItem.toObject(),
domain: checkbox.secondaryData.domain
? game.i18n.localize(
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
.label
)
: null,
subclass: subclass ? subclass.name : null
}
...multiclassItem.toObject(),
domain: checkbox.secondaryData.domain
? game.i18n.localize(
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
.label
)
: null,
subclass: subclass ? subclass.name : null
}
: {};
break;
}

View file

@ -77,9 +77,9 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
achievementExperiences = level.achievements.experiences
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
if (experience.name) acc.push(experience);
return acc;
}, [])
if (experience.name) acc.push(experience);
return acc;
}, [])
: [];
}
context.achievements = {
@ -155,15 +155,15 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
vicious: {
damage: advancement.vicious?.damage
? {
old: actorDamageDice,
new: advancement.vicious.damage
}
old: actorDamageDice,
new: advancement.vicious.damage
}
: null,
range: advancement.vicious?.range
? {
old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`),
new: game.i18n.localize(advancement.vicious.range.label)
}
old: game.i18n.localize(`DAGGERHEART.CONFIG.Range.${actorRange}.name`),
new: game.i18n.localize(advancement.vicious.range.label)
}
: null
},
simple: advancement.simple ?? {}

View file

@ -135,192 +135,6 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
context.showTabs = this.tabGroups.primary !== 'summary';
break;
const actorArmor = this.actor.system.armor;
const levelKeys = Object.keys(this.levelup.levels);
let achivementProficiency = 0;
const achievementCards = [];
let achievementExperiences = [];
for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey];
if (Number(levelKey) < this.levelup.startLevel) continue;
achivementProficiency += level.achievements.proficiency ?? 0;
const cards = level.achievements.domainCards ? Object.values(level.achievements.domainCards) : null;
if (cards) {
for (var card of cards) {
const itemCard = await foundry.utils.fromUuid(card.uuid);
achievementCards.push(itemCard);
}
}
achievementExperiences = level.achievements.experiences
? Object.values(level.achievements.experiences).reduce((acc, experience) => {
if (experience.name) acc.push(experience);
return acc;
}, [])
: [];
}
context.achievements = {
proficiency: {
old: this.actor.system.proficiency,
new: this.actor.system.proficiency + achivementProficiency,
shown: achivementProficiency > 0
},
damageThresholds: {
major: {
old: this.actor.system.damageThresholds.major,
new: this.actor.system.damageThresholds.major + changedActorLevel - currentActorLevel
},
severe: {
old: this.actor.system.damageThresholds.severe,
new:
this.actor.system.damageThresholds.severe +
(actorArmor
? changedActorLevel - currentActorLevel
: (changedActorLevel - currentActorLevel) * 2)
},
unarmored: !actorArmor
},
domainCards: {
values: achievementCards,
shown: achievementCards.length > 0
},
experiences: {
values: achievementExperiences
}
};
const advancement = {};
for (var levelKey of levelKeys) {
const level = this.levelup.levels[levelKey];
if (Number(levelKey) < this.levelup.startLevel) continue;
for (var choiceKey of Object.keys(level.choices)) {
const choice = level.choices[choiceKey];
for (var checkbox of Object.values(choice)) {
switch (choiceKey) {
case 'proficiency':
case 'hitPoint':
case 'stress':
case 'evasion':
advancement[choiceKey] = advancement[choiceKey]
? advancement[choiceKey] + Number(checkbox.value)
: Number(checkbox.value);
break;
case 'trait':
if (!advancement[choiceKey]) advancement[choiceKey] = {};
for (var traitKey of checkbox.data) {
if (!advancement[choiceKey][traitKey]) advancement[choiceKey][traitKey] = 0;
advancement[choiceKey][traitKey] += 1;
}
break;
case 'domainCard':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
if (checkbox.data.length === 1) {
const choiceItem = await foundry.utils.fromUuid(checkbox.data[0]);
advancement[choiceKey].push(choiceItem.toObject());
}
break;
case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
const data = checkbox.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(
x => x === data
);
return this.actor.system.experiences[experience]?.description ?? '';
});
advancement[choiceKey].push({ data: data, value: checkbox.value });
break;
case 'subclass':
if (checkbox.data[0]) {
const subclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
if (!advancement[choiceKey]) advancement[choiceKey] = [];
advancement[choiceKey].push({
...subclassItem.toObject(),
featureLabel: game.i18n.localize(
subclassFeatureLabels[Number(checkbox.secondaryData.featureState)]
)
});
}
break;
case 'multiclass':
const multiclassItem = await foundry.utils.fromUuid(checkbox.data[0]);
const subclass = multiclassItem
? await foundry.utils.fromUuid(checkbox.secondaryData.subclass)
: null;
advancement[choiceKey] = multiclassItem
? {
...multiclassItem.toObject(),
domain: checkbox.secondaryData.domain
? game.i18n.localize(
CONFIG.DH.DOMAIN.allDomains()[checkbox.secondaryData.domain]
.label
)
: null,
subclass: subclass ? subclass.name : null
}
: {};
break;
}
}
}
}
context.advancements = {
statistics: {
proficiency: {
old: context.achievements.proficiency.new,
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
},
hitPoints: {
old: this.actor.system.resources.hitPoints.max,
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
},
stress: {
old: this.actor.system.resources.stress.max,
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion,
new: this.actor.system.evasion + (advancement.evasion ?? 0)
}
},
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
if (advancement.trait?.[traitKey]) {
if (!acc) acc = {};
acc[traitKey] = {
label: game.i18n.localize(abilities[traitKey].label),
old: this.actor.system.traits[traitKey].value,
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
};
}
return acc;
}, null),
domainCards: advancement.domainCard ?? [],
experiences:
advancement.experience?.flatMap(x => x.data.map(data => ({ name: data, modifier: x.value }))) ??
[],
multiclass: advancement.multiclass,
subclass: advancement.subclass
};
context.advancements.statistics.proficiency.shown =
context.advancements.statistics.proficiency.new > context.advancements.statistics.proficiency.old;
context.advancements.statistics.hitPoints.shown =
context.advancements.statistics.hitPoints.new > context.advancements.statistics.hitPoints.old;
context.advancements.statistics.stress.shown =
context.advancements.statistics.stress.new > context.advancements.statistics.stress.old;
context.advancements.statistics.evasion.shown =
context.advancements.statistics.evasion.new > context.advancements.statistics.evasion.old;
context.advancements.statistics.shown =
context.advancements.statistics.proficiency.shown ||
context.advancements.statistics.hitPoints.shown ||
context.advancements.statistics.stress.shown ||
context.advancements.statistics.evasion.shown;
break;
}
return context;
@ -358,14 +172,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,
@ -384,37 +198,35 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
this._dragDrop.forEach(d => d.bind(htmlElement));
}
tagifyUpdate =
type =>
async (_, { option, removed }) => {
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
(acc, choiceKey) => {
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];
Object.keys(choice).forEach(checkboxNr => {
const checkbox = choice[checkboxNr];
if (
choiceKey === type &&
(removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount)
) {
acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`;
}
});
tagifyUpdate = type => async (_, { option, removed }) => {
const updatePath = Object.keys(this.levelup.levels[this.levelup.currentLevel].choices).reduce(
(acc, choiceKey) => {
const choice = this.levelup.levels[this.levelup.currentLevel].choices[choiceKey];
Object.keys(choice).forEach(checkboxNr => {
const checkbox = choice[checkboxNr];
if (
choiceKey === type &&
(removed ? checkbox.data.includes(option) : checkbox.data.length < checkbox.amount)
) {
acc = `levels.${this.levelup.currentLevel}.choices.${choiceKey}.${checkboxNr}.data`;
}
});
return acc;
},
null
);
return acc;
},
null
);
if (!updatePath) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft'));
return;
}
if (!updatePath) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noSelectionsLeft'));
return;
}
const currentData = foundry.utils.getProperty(this.levelup, updatePath);
const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option];
await this.levelup.updateSource({ [updatePath]: updatedData });
this.render();
};
const currentData = foundry.utils.getProperty(this.levelup, updatePath);
const updatedData = removed ? currentData.filter(x => x !== option) : [...currentData, option];
await this.levelup.updateSource({ [updatePath]: updatedData });
this.render();
};
static async updateForm(event, _, formData) {
const { levelup } = foundry.utils.expandObject(formData.object);
@ -593,10 +405,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const domainCards = this.levelup.levels[this.levelup.currentLevel].achievements.domainCards;
const illegalDomainCards = option.secondaryData.domain
? Object.keys(domainCards)
.map(key => ({ ...domainCards[key], key }))
.filter(
x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain
)
.map(key => ({ ...domainCards[key], key }))
.filter(
x => x.uuid && foundry.utils.fromUuidSync(x.uuid).system.domain === option.secondaryData.domain
)
: [];
illegalDomainCards.forEach(card => {
update[`levels.${this.levelup.currentLevel}.achievements.domainCards.${card.key}.uuid`] = null;

View file

@ -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;
@ -251,8 +251,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
const configTitle = isDowntime
? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMove')
: type === 'armorFeatures'
? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature')
: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature');
? game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.armorFeature')
: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.weaponFeature');
const editedBase = await game.system.api.applications.sheetConfigs.SettingFeatureConfig.configure(
configTitle,

View file

@ -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'

View file

@ -168,8 +168,8 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
updatedEffects = deleteEffect
? currentEffects.filter(x => x.id !== effectData.id)
: existingEffectIndex === -1
? [...currentEffects, effectData]
: currentEffects.with(existingEffectIndex, effectData);
? [...currentEffects, effectData]
: currentEffects.with(existingEffectIndex, effectData);
await this.updateMove({
[`${this.movePath}.effects`]: updatedEffects
});
@ -235,9 +235,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
return this.hasEffects
? tabs
: Object.keys(tabs).reduce((acc, key) => {
if (key !== 'effects') acc[key] = tabs[key];
return acc;
}, {});
if (key !== 'effects') acc[key] = tabs[key];
return acc;
}, {});
}
/** @override */

View file

@ -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
}
}
]
};

View file

@ -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"]',
@ -777,11 +785,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
filter:
key === 'subclasses'
? {
'system.linkedClass.uuid': {
key: 'system.linkedClass.uuid',
value: this.document.system.class.value?._stats.compendiumSource
}
}
'system.linkedClass.uuid': {
key: 'system.linkedClass.uuid',
value: this.document.system.class.value?._stats.compendiumSource
}
}
: undefined,
render: {
noFolder: true
@ -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
});

View file

@ -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 = {

View file

@ -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) {
@ -173,9 +162,9 @@ export default class Party extends DHBaseActorSheet {
difficulty: actor.system.difficulty,
traits: actor.system.traits
? traits.map(t => ({
label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`),
value: actor.system.traits[t].value
}))
label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`),
value: actor.system.traits[t].value
}))
: null,
weapons
});
@ -317,7 +306,7 @@ export default class Party extends DHBaseActorSheet {
static async downtimeMoveQuery({ actorId, downtimeType }) {
const actor = await foundry.utils.fromUuid(actorId);
if (!actor || !actor?.isOwner) reject();
if (!actor || !actor?.isOwner) return;
new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({
force: true
});

View file

@ -722,10 +722,10 @@ export default function DHApplicationMixin(Base) {
const parent = featureOnCharacter
? this.document.parent
: parentIsItem && documentClass === 'Item'
? type === 'action'
? this.document.system
: null
: this.document;
? type === 'action'
? this.document.system
: null
: this.document;
let systemData = {};
if (featureOnCharacter) {

View file

@ -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 */
/* -------------------------------------------- */
@ -340,7 +377,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
action: 'update',
documentName: 'Item',
parent: targetActor,
updates: [{ '_id': existing.id, 'system.quantity': existing.system.quantity + quantity }]
updates: [{ _id: existing.id, 'system.quantity': existing.system.quantity + quantity }]
});
} else {
const itemsToCreate = [];
@ -373,7 +410,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
action: 'update',
documentName: 'Item',
parent: originActor,
updates: [{ '_id': item.id, 'system.quantity': item.system.quantity - quantity }]
updates: [{ _id: item.id, 'system.quantity': item.system.quantity - quantity }]
});
}

View file

@ -29,16 +29,6 @@ export default function ItemAttachmentSheet(Base) {
}
};
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
context.attachedItems = await prepareAttachmentContext(this.document);
}
return context;
}
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);

View file

@ -13,8 +13,8 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
return document.type === 'adversary'
? game.i18n.localize(adversaryTypes[document.system.type]?.label ?? 'TYPES.Actor.adversary')
: document.type === 'environment'
? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment')
: null;
? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment')
: null;
};
}

View file

@ -41,8 +41,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
const advantage = rollCommand.advantage
? CONFIG.DH.ACTIONS.advantageState.advantage.value
: rollCommand.disadvantage
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
: undefined;
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
: undefined;
const difficulty = rollCommand.difficulty;
const grantResources = rollCommand.grantResources;
@ -50,8 +50,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
const title =
(flavor ?? traitValue)
? game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: game.i18n.localize(SYSTEM.ACTOR.abilities[traitValue].label)
})
ability: game.i18n.localize(CONFIG.DH.ACTOR.abilities[traitValue].label)
})
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
enrichedDualityRoll({

View file

@ -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(

View file

@ -56,8 +56,8 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
: null;
const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic;
acc[key] = {
@ -148,11 +148,11 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
}
async gmSetSetting(data) {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
}

View file

@ -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)
},
@ -101,8 +101,8 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
: 'DAGGERHEART.UI.Countdowns.loop'
: null;
const loopDisabled =
!countdownEditable ||
@ -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;
@ -180,12 +181,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
? Number(progressMax) + 1
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
? Math.max(Number(progressMax) - 1, 0)
: progressMax;
? Math.max(Number(progressMax) - 1, 0)
: progressMax;
await waitForDiceSoNice(message);
await settings.updateSource({
[`countdowns.${target.id}.progress`]: {
[`countdowns.${countdownId}.progress`]: {
current: newMax,
start: newMax
}
@ -199,22 +200,23 @@ 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
});
}
static async gmSetSetting(data) {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
}

View file

@ -67,10 +67,10 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
const actor = token
? token.actor
: canvas.tokens.controlled.length === 0
? !game.user.isGM
? game.user.character
: null
: canvas.tokens.controlled[0].actor;
? !game.user.isGM
? game.user.character
: null
: canvas.tokens.controlled[0].actor;
return getIconVisibleActiveEffects(actor?.getActiveEffects() ?? []);
};

View file

@ -155,15 +155,15 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
const targetEdge = this.#getEdgeBoundary(targetBounds, originPoint, targetPoint);
const adjustedOriginPoint = originEdge
? canvas.grid.getTopLeftPoint({
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
})
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
})
: originPoint;
const adjustDestinationPoint = targetEdge
? canvas.grid.getTopLeftPoint({
x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
})
x: targetEdge.x + Math.sign(targetPoint.x - targetEdge.x),
y: targetEdge.y + Math.sign(targetPoint.y - targetEdge.y)
})
: targetPoint;
const distance = canvas.grid.measurePath([
{ ...adjustedOriginPoint, elevation: 0 },

View file

@ -127,8 +127,8 @@ export const typeConfig = {
isSecondary
? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short'
: isSecondary === false
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
: '-'
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
: '-'
},
{
key: 'system.tier',

View file

@ -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)

View file

@ -144,8 +144,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return this.item instanceof DhpActor
? this.item
: this.item?.parent instanceof DhpActor
? this.item.parent
: null;
? this.item.parent
: null;
}
/**
@ -223,7 +223,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
* @returns {object}
*/
async use(event, configOptions = {}) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (!this.actor) throw new Error('An Action can\'t be used outside of an Actor context.');
let config = this.prepareConfig(event, configOptions);
if (!config) return;
@ -300,17 +300,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
const groupAttackTokens = this.damage.groupAttack
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
this.actor.id,
this.damage.groupAttack
)
this.actor.id,
this.damage.groupAttack
)
: null;
config.damageOptions = {
groupAttack: this.damage.groupAttack
? {
numAttackers: Math.max(groupAttackTokens.length, 1),
range: this.damage.groupAttack
}
numAttackers: Math.max(groupAttackTokens.length, 1),
range: this.damage.groupAttack
}
: null
};
}

View file

@ -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;

View file

@ -90,13 +90,13 @@ export default class BeastformEffect extends BaseEffect {
...baseUpdate,
x,
y,
'texture': {
texture: {
enabled: this.characterTokenData.usesDynamicToken,
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg,
scaleX: this.characterTokenData.tokenSize.scale,
scaleY: this.characterTokenData.tokenSize.scale
},
'ring': {
ring: {
subject: {
texture:
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg

View file

@ -166,10 +166,10 @@ export default class ArmorChange extends foundry.abstract.DataModel {
value:
change.type === 'armor'
? {
...change.value,
current: Math.min(change.value.current, newMax),
max: newMax
}
...change.value,
current: Math.min(change.value.current, newMax),
max: newMax
}
: change.value
}))
];

View file

@ -315,8 +315,8 @@ export default class DhCharacter extends DhCreature {
return currentLevel === 1
? 1
: Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find(
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
).tier;
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
).tier;
}
get ancestry() {
@ -520,20 +520,20 @@ export default class DhCharacter extends DhCreature {
if (armorSource.type === 'armor') {
armorUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
_id: armorSource.id,
'system.armor.current': armorSource.system.armor.current + usedArmorChange
});
} else {
effectUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
_id: armorSource.id,
'system.changes': armorSource.system.changes.map(change => ({
...change,
value:
change.type === 'armor'
? {
...change.value,
current: armorSource.system.armorChange.value.current + usedArmorChange
}
...change.value,
current: armorSource.system.armorChange.value.current + usedArmorChange
}
: change.value
}))
});
@ -621,21 +621,21 @@ export default class DhCharacter extends DhCreature {
},
...(multiclassFeatures.length
? {
multiclassFeatures: {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`,
type: 'multiclass',
values: multiclassFeatures
}
}
multiclassFeatures: {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`,
type: 'multiclass',
values: multiclassFeatures
}
}
: {}),
...(multiclassSubclassFeatures.length
? {
multiclassSubclassFeatures: {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`,
type: 'multiclassSubclass',
values: multiclassSubclassFeatures
}
}
multiclassSubclassFeatures: {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`,
type: 'multiclassSubclass',
values: multiclassSubclassFeatures
}
}
: {}),
companionFeatures: {
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'),
@ -659,8 +659,8 @@ export default class DhCharacter extends DhCreature {
(this.primaryWeapon && this.secondaryWeapon)
? burden.twoHanded.value
: this.primaryWeapon || this.secondaryWeapon
? burden.oneHanded.value
: null;
? burden.oneHanded.value
: null;
}
get deathMoveViable() {
@ -726,8 +726,8 @@ export default class DhCharacter extends DhCreature {
currentLevel === 1
? null
: Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers).find(
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
).tier;
tier => currentLevel >= tier.levels.start && currentLevel <= tier.levels.end
).tier;
if (game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto) {
for (let levelKey in this.levelData.levelups) {
const level = this.levelData.levelups[levelKey];

View file

@ -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;

View file

@ -22,12 +22,12 @@ export class DhCompanionLevelup extends foundry.abstract.DataModel {
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
const experiences = initialAchievements.experience
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = {
name: '',
modifier: initialAchievements.experience.modifier
};
return acc;
}, {})
acc[foundry.utils.randomID()] = {
name: '',
modifier: initialAchievements.experience.modifier
};
return acc;
}, {})
: {};
const currentChoices = pcLevelData.levelups[i]?.selections?.length;
@ -302,9 +302,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel {
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
domainCards: levelData.achievements?.domainCards
? levelData.achievements.domainCards.reduce((acc, card, index) => {
acc[index] = { ...card };
return acc;
}, {})
acc[index] = { ...card };
return acc;
}, {})
: (achievements.domainCards ?? {}),
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
},

View file

@ -77,11 +77,11 @@ export class DhCountdown extends foundry.abstract.DataModel {
static defaultCountdown(type, playerHidden) {
const ownership = playerHidden
? game.users.reduce((acc, user) => {
if (!user.isGM) {
acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE;
}
return acc;
}, {})
if (!user.isGM) {
acc[user.id] = CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE;
}
return acc;
}, {})
: undefined;
return {
@ -102,8 +102,8 @@ export class DhCountdown extends foundry.abstract.DataModel {
value: user.isGM
? CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER
: this.ownership.players[user.id] && this.ownership.players[user.id].type !== -1
? this.ownership.players[user.id].type
: this.ownership.default,
? this.ownership.players[user.id].type
: this.ownership.default,
isGM: user.isGM
};

View file

@ -105,8 +105,8 @@ export default class BeastformField extends fields.SchemaField {
baseSize === 'custom'
? 'custom'
: (Object.keys(CONFIG.DH.ACTOR.tokenSize).find(
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
) ?? baseSize);
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
) ?? baseSize);
formData.system.tokenSize = {
...evolvedData.form.system.tokenSize,
size: evolvedSize

View file

@ -116,8 +116,8 @@ export default class CostField extends fields.ArrayField {
c.key === 'fear'
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
: resources[c.key].isReversed
? resources[c.key].max - resources[c.key].value
: resources[c.key].value;
? resources[c.key].max - resources[c.key].value
: resources[c.key].value;
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
return c;
});
@ -149,8 +149,8 @@ export default class CostField extends fields.ArrayField {
!resources[c.key]
? a
: a && resources[c.key].isReversed
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
: resources[c.key]?.value >= (c.total ?? c.value),
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
: resources[c.key]?.value >= (c.total ?? c.value),
true
);
}

View file

@ -87,11 +87,11 @@ export default class CountdownField extends fields.ArrayField {
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
countdownSetting.toObject()
),
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
);
game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.Refresh,
data: { refreshType: RefreshType.Countdown }
});
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
},
data,

View file

@ -61,11 +61,11 @@ export default class EffectsField extends fields.ArrayField {
token: messageToken,
conditionImmunities: Object.values(conditionImmunities).some(x => x)
? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', {
immunities: Object.keys(conditionImmunities)
.filter(x => conditionImmunities[x])
.map(x => game.i18n.localize(conditions[x].name))
.join(', ')
})
immunities: Object.keys(conditionImmunities)
.filter(x => conditionImmunities[x])
.map(x => game.i18n.localize(conditions[x].name))
.join(', ')
})
: null
});

View file

@ -69,11 +69,11 @@ export default class SaveField extends fields.SchemaField {
game.user === actor.owner
? SaveField.rollSave.call(this, actor, event)
: actor.owner.query('reactionRoll', {
actionId: this.uuid,
actorId: actor.uuid,
event,
message
});
actionId: this.uuid,
actorId: actor.uuid,
event,
message
});
const result = await rollSave;
await SaveField.updateSaveMessage.call(this, result, message, target.id);
subResolve();
@ -97,8 +97,8 @@ export default class SaveField extends fields.SchemaField {
const title = actor.isNPC
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: game.i18n.localize(abilities[this.save.trait]?.label)
}),
ability: game.i18n.localize(abilities[this.save.trait]?.label)
}),
rollConfig = {
event,
title,

View file

@ -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);

View file

@ -208,8 +208,8 @@ export default class DHBeastform extends BaseDataItem {
const autoTokenSize =
this.tokenSize.size !== 'custom'
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[
this.tokenSize.size
]
this.tokenSize.size
]
: null;
const width = autoTokenSize ?? this.tokenSize.width;
const height = autoTokenSize ?? this.tokenSize.height;

View file

@ -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);

View file

@ -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));

View file

@ -19,12 +19,12 @@ export class DhLevelup extends foundry.abstract.DataModel {
const initialAchievements = i === tier.levels.start ? tier.initialAchievements : {};
const experiences = initialAchievements.experience
? [...Array(initialAchievements.experience.nr).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = {
name: '',
modifier: initialAchievements.experience.modifier
};
return acc;
}, {})
acc[foundry.utils.randomID()] = {
name: '',
modifier: initialAchievements.experience.modifier
};
return acc;
}, {})
: {};
const domainCards = [...Array(tier.domainCardByLevel).keys()].reduce((acc, _) => {
@ -298,9 +298,9 @@ export class DhLevelupLevel extends foundry.abstract.DataModel {
experiences: levelData.achievements?.experiences ?? achievements.experiences ?? {},
domainCards: levelData.achievements?.domainCards
? levelData.achievements.domainCards.reduce((acc, card, index) => {
acc[index] = { ...card };
return acc;
}, {})
acc[index] = { ...card };
return acc;
}, {})
: (achievements.domainCards ?? {}),
proficiency: levelData.achievements?.proficiency ?? achievements.proficiency ?? null
},

View file

@ -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;
@ -103,9 +104,9 @@ export default class DHRoll extends Roll {
if (action?.chatDisplay) {
actionDescription = action
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(action.description, {
relativeTo: config.data,
rollData: config.data.getRollData?.() ?? {}
})
relativeTo: config.data,
rollData: config.data.getRollData?.() ?? {}
})
: null;
config.actionChatMessageHandled = true;
}

View file

@ -109,10 +109,10 @@ export default class DualityRoll extends D20Roll {
const label = this.guaranteedCritical
? 'DAGGERHEART.GENERAL.guaranteedCriticalSuccess'
: this.isCritical
? 'DAGGERHEART.GENERAL.criticalSuccess'
: this.withHope
? 'DAGGERHEART.GENERAL.hope'
: 'DAGGERHEART.GENERAL.fear';
? 'DAGGERHEART.GENERAL.criticalSuccess'
: this.withHope
? 'DAGGERHEART.GENERAL.hope'
: 'DAGGERHEART.GENERAL.fear';
return game.i18n.localize(label);
}
@ -147,8 +147,8 @@ export default class DualityRoll extends D20Roll {
const advDieClass = this.hasAdvantage
? game.system.api.dice.diceTypes.AdvantageDie
: this.hasDisadvantage
? game.system.api.dice.diceTypes.DisadvantageDie
: null;
? game.system.api.dice.diceTypes.DisadvantageDie
: null;
if (advDieClass) {
const advDie = new advDieClass({ faces: this.advantageFaces, number: this.advantageNumber });
if (this.terms.length < 4) {

View file

@ -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;

View file

@ -175,8 +175,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
return model instanceof documentClass
? model
: model.parent
? this.#resolveParentDocument(model.parent, documentClass)
: null;
? this.#resolveParentDocument(model.parent, documentClass)
: null;
}
static getChangeValue(model, change, effect) {

View file

@ -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 */

View file

@ -9,9 +9,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
actor && this.isContentVisible
? actor
: {
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
name: ''
};
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
name: ''
};
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
const html = await super.renderHTML({ actor: actorData, author: this.author });
@ -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);
}
@ -270,14 +290,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
behaviors:
effects.length > 0
? [
{
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
type: 'applyActiveEffect',
system: {
effects: effects
}
}
]
{
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
type: 'applyActiveEffect',
system: {
effects: effects
}
}
]
: [],
displayMeasurements: true,
locked: false,

View file

@ -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.');
@ -97,8 +98,8 @@ export default class DHItem extends foundry.documents.Item {
isInventoryItem === true
? 'Inventory Items' //TODO localize
: isInventoryItem === false
? 'Character Items' //TODO localize
: 'Other'; //TODO localize
? 'Character Items' //TODO localize
: 'Other'; //TODO localize
return { value: type, label, group };
}

View file

@ -324,7 +324,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
}
let x = 0.5 * bottom;
let y = 0.25;
for (let k = width - bottom; k--; ) {
for (let k = width - bottom; k--;) {
points.push(x, y);
x += 0.5;
y -= 0.25;
@ -333,7 +333,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
y += 0.25;
}
points.push(x, y);
for (let k = bottom; k--; ) {
for (let k = bottom; k--;) {
y += 0.5;
points.push(x, y);
x += 0.5;
@ -341,14 +341,14 @@ export default class DHToken extends CONFIG.Token.documentClass {
points.push(x, y);
}
y += 0.5;
for (let k = top; k--; ) {
for (let k = top; k--;) {
points.push(x, y);
x -= 0.5;
y += 0.25;
points.push(x, y);
y += 0.5;
}
for (let k = width - top; k--; ) {
for (let k = width - top; k--;) {
points.push(x, y);
x -= 0.5;
y += 0.25;
@ -357,7 +357,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
y -= 0.25;
}
points.push(x, y);
for (let k = top; k--; ) {
for (let k = top; k--;) {
y -= 0.5;
points.push(x, y);
x -= 0.5;
@ -365,7 +365,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
points.push(x, y);
}
y -= 0.5;
for (let k = bottom; k--; ) {
for (let k = bottom; k--;) {
points.push(x, y);
x += 0.5;
y -= 0.25;

View file

@ -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;

View file

@ -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
}
]
};

View file

@ -15,8 +15,8 @@ function getDualityMessage(roll, flavor) {
(roll?.trait
? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait })
: roll?.reaction
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
: game.i18n.localize('DAGGERHEART.GENERAL.duality'));
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
: game.i18n.localize('DAGGERHEART.GENERAL.duality'));
const dataLabel = trait
? game.i18n.localize(abilities[roll.trait].label)
@ -25,14 +25,14 @@ function getDualityMessage(roll, flavor) {
const advantage = roll?.advantage
? CONFIG.DH.ACTIONS.advantageState.advantage.value
: roll?.disadvantage
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
: undefined;
? CONFIG.DH.ACTIONS.advantageState.disadvantage.value
: undefined;
const advantageLabel =
advantage === CONFIG.DH.ACTIONS.advantageState.advantage.value
? 'Advantage'
: advantage === CONFIG.DH.ACTIONS.advantageState.disadvantage.value
? 'Disadvantage'
: undefined;
? 'Disadvantage'
: undefined;
const dualityElement = document.createElement('span');
dualityElement.innerHTML = `

View file

@ -8,8 +8,8 @@ export default function DhTemplateEnricher(match, _options) {
const range =
params.range && Number.isNaN(Number(params.range))
? Object.values(CONFIG.DH.GENERAL.templateRanges).find(
x => x.id.toLowerCase() === params.range || x.short === params.range
)?.id
x => x.id.toLowerCase() === params.range || x.short === params.range
)?.id
: params.range;
if (!CONFIG.DH.GENERAL.templateTypes[type] || !range) return match[0];

View file

@ -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(':');

View file

@ -108,9 +108,9 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
const options = Array.isArray(baseOptions)
? baseOptions
: Object.keys(baseOptions).map(optionKey => ({
...baseOptions[optionKey],
id: optionKey
}));
...baseOptions[optionKey],
id: optionKey
}));
const tagifyElement = new Tagify(element, {
tagTextProp: 'name',
@ -605,8 +605,8 @@ export function calculateExpectedValue(formulaOrTerms) {
const terms = Array.isArray(formulaOrTerms)
? formulaOrTerms
: typeof formulaOrTerms === 'string'
? parseTermsFromSimpleFormula(formulaOrTerms)
: [formulaOrTerms];
? parseTermsFromSimpleFormula(formulaOrTerms)
: [formulaOrTerms];
return terms.reduce((r, t) => r + (t.bonus ?? 0) + (t.diceQuantity ? (t.diceQuantity * (t.faces + 1)) / 2 : 0), 0);
}
@ -656,8 +656,8 @@ export async function RefreshFeatures(
'resource.value': increasing
? 0
: game.system.api.documents.DhActiveEffect.effectSafeEval(
Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
)
Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
)
};
}
if (item.system.metadata?.hasActions) {
@ -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;

View file

@ -24,8 +24,8 @@ export async function runMigrations() {
const { originItemType, isMulticlass, identifier } = item.system;
const base = originItemType
? actor.items.find(
x => x.type === originItemType && Boolean(isMulticlass) === Boolean(x.system.isMulticlass)
)
x => x.type === originItemType && Boolean(isMulticlass) === Boolean(x.system.isMulticlass)
)
: null;
if (base) {
const feature = base.system.features.find(x => x.item && x.item.uuid === item.uuid);

551
package-lock.json generated
View file

@ -13,18 +13,20 @@
"rollup": "^4.40.0"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@foundryvtt/foundryvtt-cli": "^1.0.2",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@stylistic/eslint-plugin": "^5.10.0",
"concurrently": "^8.2.2",
"eslint": "^10.2.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"postcss": "^8.4.32",
"prettier": "^3.5.3",
"rollup-plugin-postcss": "^4.0.2"
"rollup-plugin-postcss": "^4.0.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1"
}
},
"node_modules/@babel/runtime": {
@ -158,6 +160,27 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
"node_modules/@eslint/js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
"integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://eslint.org/donate"
},
"peerDependencies": {
"eslint": "^10.0.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/@eslint/object-schema": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
@ -420,19 +443,6 @@
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true
},
"node_modules/@pkgr/core": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz",
"integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/pkgr"
}
},
"node_modules/@rollup/plugin-commonjs": {
"version": "25.0.8",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.8.tgz",
@ -761,6 +771,58 @@
"util": "^0.12.4"
}
},
"node_modules/@stylistic/eslint-plugin": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz",
"integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/types": "^8.56.0",
"eslint-visitor-keys": "^4.2.1",
"espree": "^10.4.0",
"estraverse": "^5.3.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"eslint": "^9.0.0 || ^10.0.0"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
"integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/espree": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
"integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.15.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@trysound/sax": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
@ -795,6 +857,288 @@
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz",
"integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/type-utils": "8.60.1",
"@typescript-eslint/utils": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.60.1",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
"integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz",
"integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz",
"integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.60.1",
"@typescript-eslint/types": "^8.60.1",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz",
"integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz",
"integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz",
"integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/utils": "8.60.1",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz",
"integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz",
"integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.60.1",
"@typescript-eslint/tsconfig-utils": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/visitor-keys": "8.60.1",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz",
"integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz",
"integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.60.1",
"@typescript-eslint/types": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz",
"integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.60.1",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@yaireo/tagify": {
"version": "4.35.1",
"resolved": "https://registry.npmjs.org/@yaireo/tagify/-/tagify-4.35.1.tgz",
@ -1853,10 +2197,11 @@
}
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
@ -2241,37 +2586,6 @@
}
}
},
"node_modules/eslint-plugin-prettier": {
"version": "5.5.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz",
"integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==",
"dev": true,
"license": "MIT",
"dependencies": {
"prettier-linter-helpers": "^1.0.1",
"synckit": "^0.11.12"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint-plugin-prettier"
},
"peerDependencies": {
"@types/eslint": ">=8.0.0",
"eslint": ">=8.0.0",
"eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
"prettier": ">=3.0.0"
},
"peerDependenciesMeta": {
"@types/eslint": {
"optional": true
},
"eslint-config-prettier": {
"optional": true
}
}
},
"node_modules/eslint-scope": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
@ -2511,13 +2825,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-diff": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
"integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@ -2554,6 +2861,24 @@
"reusify": "^1.0.4"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -5217,34 +5542,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
"integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-diff": "^1.1.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
@ -6053,22 +6350,6 @@
"node": ">= 10"
}
},
"node_modules/synckit": {
"version": "0.11.12",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz",
"integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@pkgr/core": "^0.2.9"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/synckit"
}
},
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
@ -6116,6 +6397,23 @@
"node": ">=18"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -6147,6 +6445,19 @@
"tree-kill": "cli.js"
}
},
"node_modules/ts-api-utils": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
"integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18.12"
},
"peerDependencies": {
"typescript": ">=4.8.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -6171,6 +6482,44 @@
"node": ">= 0.8.0"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/typescript-eslint": {
"version": "8.60.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz",
"integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.60.1",
"@typescript-eslint/parser": "8.60.1",
"@typescript-eslint/typescript-estree": "8.60.1",
"@typescript-eslint/utils": "8.60.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",

View file

@ -24,18 +24,20 @@
"prepare": "husky"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@foundryvtt/foundryvtt-cli": "^1.0.2",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@stylistic/eslint-plugin": "^5.10.0",
"concurrently": "^8.2.2",
"eslint": "^10.2.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.5.0",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"postcss": "^8.4.32",
"prettier": "^3.5.3",
"rollup-plugin-postcss": "^4.0.2"
"rollup-plugin-postcss": "^4.0.2",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1"
},
"lint-staged": {
"**/*": "eslint --fix"

View file

@ -1,19 +1,11 @@
@import './less/sheets/index.less';
@import './less/sheets-settings/index.less';
@import './less/dialog/index.less';
@import './less//hud/index.less';
@import './less/utils/colors.less';
@import './less/utils/fonts.less';
@import './less/hud/index.less';
@import './less/utils/index.less';
@import './less/global/index.less';
@import './less/ui/index.less';
@import './less/ux/index.less';
@import '../build/tagify.css';
@import './less/utils/mixin.less';

View file

@ -0,0 +1 @@
@import "./action-list.less";

View file

@ -0,0 +1 @@
@import "./sheet.less";

View file

@ -0,0 +1 @@
@import "./sheet.less";

View file

@ -0,0 +1,4 @@
@import "./sheet.less";
@import "./creation-action-footer.less";
@import "./selections-container.less";
@import "./tab-navigation.less";

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1,2 @@
@import './sheets.less';
@import './damage-reduction-container.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './death-move-container.less';

View file

@ -0,0 +1 @@
@import './roll-selection.less';

View file

@ -0,0 +1 @@
@import './downtime-container.less';

View file

@ -1,44 +0,0 @@
h1 {
color: @color-text-emphatic;
font: 700 var(--font-size-24) var(--dh-font-subtitle);
text-align: center;
}
header {
--bar-color: light-dark(@dark-blue, @golden);
color: light-dark(@dark, @beige);
display: flex;
justify-content: center;
align-items: center;
&:not(:first-child) {
margin-top: var(--spacer-8);
}
span {
padding: 0 10px;
}
&:before {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%);
}
&:after {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%);
}
}
img.portrait {
border-radius: 50%;
border: none;
object-fit: cover;
object-position: center top;
width: 2.5rem;
height: 2.5rem;
}

View file

@ -1,8 +1,3 @@
.daggerheart.dialog.dh-style.views.group-roll-dialog {
.window-content {
@import "./_common.less";
}
}
@import "./initialization.less";
@import "./main.less";
@import './sheet.less';
@import './initialization.less';
@import './main.less';

View file

@ -0,0 +1,48 @@
.daggerheart.dialog.dh-style.views.group-roll-dialog {
.window-content {
h1 {
color: @color-text-emphatic;
font: 700 var(--font-size-24) var(--dh-font-subtitle);
text-align: center;
}
header {
--bar-color: light-dark(@dark-blue, @golden);
color: light-dark(@dark, @beige);
display: flex;
justify-content: center;
align-items: center;
&:not(:first-child) {
margin-top: var(--spacer-8);
}
span {
padding: 0 10px;
}
&:before {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, var(--bar-color) 100%);
}
&:after {
content: ' ';
flex: 1;
height: 1px;
background: linear-gradient(90deg, var(--bar-color) 0%, rgba(0, 0, 0, 0) 100%);
}
}
img.portrait {
border-radius: 50%;
border: none;
object-fit: cover;
object-position: center top;
width: 2.5rem;
height: 2.5rem;
}
}
}

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -1,42 +1,20 @@
@import './attribution/sheet.less';
@import './level-up/index.less';
@import './resource-dice/sheet.less';
@import './actions/action-list.less';
@import './damage-selection/sheet.less';
@import './downtime/downtime-container.less';
@import './death-move/death-move-container.less';
@import './beastform/sheet.less';
@import './character-creation/creation-action-footer.less';
@import './character-creation/selections-container.less';
@import './character-creation/sheet.less';
@import './character-creation/tab-navigation.less';
@import './dice-roll/roll-selection.less';
@import './damage-reduction/damage-reduction-container.less';
@import './damage-reduction/sheets.less';
@import './multiclass-choice/sheet.less';
@import './tag-team-dialog/initialization.less';
@import './tag-team-dialog/sheet.less';
@import './actions/index.less';
@import './attribution/index.less';
@import './beastform/index.less';
@import './character-creation/index.less';
@import './character-reset/index.less';
@import './compendiumBrowserPackDialog/index.less';
@import './damage-reduction/index.less';
@import './damage-selection/index.less';
@import './death-move/index.less';
@import './dice-roll/index.less';
@import './downtime/index.less';
@import './group-roll-dialog/index.less';
@import './image-select/sheet.less';
@import './item-transfer/sheet.less';
@import './settings/change-currency-icon.less';
@import './risk-it-all/sheet.less';
@import './character-reset/sheet.less';
@import './compendiumBrowserPackDialog/sheet.less';
@import './level-up/index.less';
@import './resource-dice/index.less';
@import './multiclass-choice/index.less';
@import './tag-team-dialog/index.less';
@import './image-select/index.less';
@import './item-transfer/index.less';
@import './settings/index.less';
@import './risk-it-all/index.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

View file

@ -0,0 +1 @@
@import './change-currency-icon.less';

View file

@ -0,0 +1,2 @@
@import './sheet.less';
@import './initialization.less';

View file

@ -1 +1 @@
@import './token-hud/token-hud.less';
@import './token-hud/index.less';

View file

@ -0,0 +1 @@
@import './token-hud.less';

View file

@ -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;

View file

@ -0,0 +1,3 @@
@import './sheet.less';
@import './experiences.less';
@import './features.less';

View file

@ -0,0 +1 @@
@import './sheet.less';

Some files were not shown because too many files have changed in this diff Show more