mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-06 04:44:16 +02:00
Compare commits
37 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4428fd5be | ||
|
|
6312a171e2 | ||
|
|
3527fd7959 | ||
|
|
f0a7539018 | ||
|
|
5be79f4ab8 | ||
|
|
2fc5b01f09 | ||
|
|
52b81de11f | ||
|
|
c0c9095847 | ||
|
|
5ac4fc3b9c | ||
|
|
6747be49b2 | ||
|
|
77c5cfcbb7 | ||
|
|
5dbcd94480 | ||
|
|
d98a7c951e | ||
|
|
3c36c5747d | ||
|
|
bcf274f1d0 | ||
|
|
df4a2c5d57 | ||
|
|
646ebc8bdf | ||
|
|
6448666579 | ||
|
|
d0c29ede56 | ||
|
|
98ce49b928 | ||
|
|
318d00b47d | ||
|
|
c8d0df87c8 | ||
|
|
983f48b415 | ||
|
|
bfd483698b | ||
|
|
3eb33a71af | ||
|
|
3fbc1e97c6 | ||
|
|
729e8bca42 | ||
|
|
53f15a7fde | ||
|
|
c23ac61ee5 | ||
|
|
d3141059ac | ||
|
|
61db7ca371 | ||
|
|
2bc1c04c93 | ||
|
|
493998cc95 | ||
|
|
251d7e4e13 | ||
|
|
a209b035c8 | ||
|
|
9487b07e43 | ||
|
|
f1a530f57f |
211 changed files with 2568 additions and 2158 deletions
|
|
@ -1,5 +1,6 @@
|
|||
[*]
|
||||
indent_size = 4
|
||||
indent_style = spaces
|
||||
end_of_line = lf
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
|
|
|
|||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
* text=auto eol=lf
|
||||
*.json text eol=lf
|
||||
13
.prettierrc
13
.prettierrc
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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**
|
||||
|
|
|
|||
1
assets/icons/documents/actors/drama-masks.svg
Normal file
1
assets/icons/documents/actors/drama-masks.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="height: 512px; width: 512px;"><path d="M0 0h512v512H0z" fill="#000" fill-opacity="1"></path><g class="" transform="translate(0,0)" style=""><path d="M418.813 30.625c-21.178 26.27-49.712 50.982-84.125 70.844-36.778 21.225-75.064 33.62-110.313 38.06a310.317 310.317 0 0 0 6.813 18.25c16.01.277 29.366-.434 36.406-1.5l9.47-1.53 8.436-1.28.22 10.186a307.48 307.48 0 0 1-1.095 18.72l56.625 8.843c.86-.095 1.713-.15 2.563-.157 11.188-.114 21.44 7.29 24.468 18.593.657 2.448.922 4.903.845 7.313 5.972-2.075 11.753-4.305 17.28-6.72l9.595-4.188 2.313 10.22a340.211 340.211 0 0 1 7.375 48.062C438.29 247.836 468.438 225.71 493 197.5c-3.22-36.73-16.154-78.04-39.125-117.813a290.509 290.509 0 0 0-2.22-3.78l-27.56 71.374c5.154.762 10.123 3.158 14.092 7.126 9.81 9.807 9.813 25.69 0 35.5-9.812 9.81-25.722 9.807-35.53 0-8.86-8.858-9.69-22.68-2.532-32.5l38.938-100.844a322.02 322.02 0 0 0-20.25-25.937zM51.842 118.72c-8.46 17.373-15.76 36.198-21.187 56.436-14.108 52.617-13.96 103.682-2.812 143.438 13.3-2.605 26.442-3.96 39.312-4.03 1.855-.012 3.688.02 5.53.06 20.857.48 40.98 4.332 59.97 11.5a355.064 355.064 0 0 1-1.656-34.218c0-27.8 3.135-54.377 9-78.937l2.47-10.407 9.655 4.562c29.467 13.98 66.194 23.424 106.28 25.22 5.136-20.05 8.19-39.78 9.408-58.75-35.198 4.83-75.387 2.766-116.407-8.22-38.363-10.272-72.314-26.78-99.562-46.656zm230.594 82.218c-1.535 10.452-3.615 21.03-6.218 31.687a312.754 312.754 0 0 0 46-3.97 24.98 24.98 0 0 1-1.532-21.748l-38.25-5.97zM105 201.375l4.156 18.22-21.594 4.905c8.75 5.174 13.353 15.703 10.594 26-3.32 12.394-16.045 19.758-28.437 16.438-12.394-3.32-19.76-16.075-16.44-28.47a23.235 23.235 0 0 1 3.126-6.874l-21.062 4.78-4.125-18.218 73.78-16.78zm388.594 22.813c-25.53 25.46-55.306 45.445-86.906 60.5.05 2.397.093 4.8.093 7.218 0 9.188-.354 18.232-1.03 27.125 16.635 1.33 32.045-1.7 45.344-9.374 25.925-14.962 40.608-45.694 42.5-85.47zm-338.844 3c-4.03 19.993-6.33 41.31-6.406 63.593l.125-.342c30.568 10.174 62.622 17.572 95.25 21.375l7.5.875.718 7.5 5.687 60.125-18.625 1.75-2.53-26.75a23.117 23.117 0 0 1-14.845.968c-12.393-3.32-19.76-16.042-16.438-28.436.285-1.06.647-2.08 1.063-3.063a496.627 496.627 0 0 1-57.406-14.53c2.69 49.62 16.154 94.04 36.094 126.656 22.366 36.588 52.13 57.78 83.968 57.78 31.838.003 61.602-21.19 83.97-57.78 19.536-31.96 32.846-75.244 35.905-123.656a499.132 499.132 0 0 1-48.25 11.656c1.914 4.57 2.415 9.78 1.033 14.938-3.322 12.394-16.045 19.758-28.438 16.437a23.01 23.01 0 0 1-2.125-.686l-2.5 26.47-18.594-1.752 5.688-60.125.72-7.5 7.498-.875c29.245-3.407 57.995-9.717 85.657-18.312v-1.594c0-21.573-2.27-42.23-6.064-61.75C351.132 242.653 313.092 250 272.312 250c-43.59 0-83.986-8.658-117.562-22.813zm-87.5 105.968c-10.87.102-21.995 1.22-33.375 3.313 12.695 31.62 33.117 53.07 59 60 16.9 4.523 34.896 2.536 52.813-5.25-4.382-13.89-7.874-28.606-10.344-43.97-21.115-9.623-43.934-14.32-68.094-14.094zm137.5 80.22h130.813c-40.082 44.594-92.623 42.844-130.813 0z" fill="#fff" fill-opacity="1"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 3 KiB |
24
daggerheart.d.ts
vendored
24
daggerheart.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,6 +196,11 @@ Hooks.once('init', () => {
|
|||
makeDefault: true,
|
||||
label: sheetLabel('TYPES.Actor.environment')
|
||||
});
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.NPC, {
|
||||
types: ['npc'],
|
||||
makeDefault: true,
|
||||
label: sheetLabel('TYPES.Actor.npc')
|
||||
});
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
||||
types: ['party'],
|
||||
makeDefault: true,
|
||||
|
|
@ -441,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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ES6",
|
||||
"target": "ES6",
|
||||
"module": "es2022",
|
||||
"target": "es2022",
|
||||
"paths": {
|
||||
"@client/*": ["./foundry/client/*"],
|
||||
"@common/*": ["./foundry/common/*"]
|
||||
|
|
|
|||
19
lang/en.json
19
lang/en.json
|
|
@ -23,6 +23,7 @@
|
|||
"companion": "Companion",
|
||||
"adversary": "Adversary",
|
||||
"environment": "Environment",
|
||||
"npc": "NPC",
|
||||
"party": "Party"
|
||||
}
|
||||
},
|
||||
|
|
@ -333,6 +334,11 @@
|
|||
},
|
||||
"newAdversary": "New Adversary"
|
||||
},
|
||||
"NPC": {
|
||||
"FIELDS": {
|
||||
"motives": { "label": "Motives" }
|
||||
}
|
||||
},
|
||||
"Party": {
|
||||
"Subtitle": {
|
||||
"character": "{community} {ancestry} | {subclass} {class}",
|
||||
|
|
@ -705,19 +711,13 @@
|
|||
},
|
||||
"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}"
|
||||
},
|
||||
"RerollDialog": {
|
||||
"title": "Reroll",
|
||||
"damageTitle": "Reroll Damage",
|
||||
"deselectDiceNotification": "Deselect one of the selected dice first",
|
||||
"acceptCurrentRolls": "Accept Current Rolls"
|
||||
},
|
||||
"ResourceDice": {
|
||||
"title": "{name} Resource",
|
||||
"rerollDice": "Reroll Dice"
|
||||
|
|
@ -3097,6 +3097,7 @@
|
|||
}
|
||||
},
|
||||
"ChatLog": {
|
||||
"rerollActionRoll": "Reroll Action",
|
||||
"rerollDamage": "Reroll Damage",
|
||||
"assignTagRoll": "Assign as Tag Roll"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
|
|||
export { default as ItemTransferDialog } from './itemTransfer.mjs';
|
||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,12 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.isGM = game.user.isGM;
|
||||
context.isEditable = this.getIsEditable();
|
||||
context.isEditable =
|
||||
game.user.isGM ||
|
||||
this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.groupRoll.participants[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
context.fields = this.party.system.schema.fields.groupRoll.fields;
|
||||
context.data = this.party.system.groupRoll;
|
||||
context.traitOptions = CONFIG.DH.ACTOR.abilities;
|
||||
|
|
@ -162,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,
|
||||
|
|
@ -265,13 +270,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
];
|
||||
}
|
||||
|
||||
getIsEditable() {
|
||||
return this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.groupRoll.participants[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
}
|
||||
|
||||
groupRollRefresh = ({ refreshType, action, parts }) => {
|
||||
if (refreshType !== RefreshType.GroupRoll) return;
|
||||
|
||||
|
|
@ -358,8 +356,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
});
|
||||
|
||||
if (!result) return;
|
||||
// todo: move logic to actor.rollTrait() or actor.diceRoll()
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const rollData = result.messageRoll.toJSON();
|
||||
delete rollData.options.messageRoll;
|
||||
|
|
|
|||
|
|
@ -1,280 +0,0 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDamageDialog.#toggleResult,
|
||||
selectRoll: RerollDamageDialog.#selectRoll,
|
||||
doReroll: RerollDamageDialog.#doReroll,
|
||||
save: RerollDamageDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDialog.#toggleResult,
|
||||
selectRoll: RerollDialog.#selectRoll,
|
||||
doReroll: RerollDialog.#doReroll,
|
||||
save: RerollDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
||||
import { itemAbleRollParse, triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli
|
|||
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
||||
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
||||
const roll = await new Roll(diceFormula).evaluate();
|
||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||
await triggerChatRollFx([roll]);
|
||||
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
||||
this.resetUsed = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.isEditable = this.getIsEditable();
|
||||
context.isEditable =
|
||||
game.user.isGM ||
|
||||
this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.tagTeam.members[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
context.fields = this.party.system.schema.fields.tagTeam.fields;
|
||||
context.data = this.party.system.tagTeam;
|
||||
context.rollTypes = CONFIG.DH.GENERAL.tagTeamRollTypes;
|
||||
|
|
@ -179,57 +184,56 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
if (Object.keys(this.party.system.tagTeam.members).includes(partId)) {
|
||||
const data = this.party.system.tagTeam.members[partId];
|
||||
const actor = game.actors.get(partId);
|
||||
|
||||
const rollOptions = [];
|
||||
const damageRollOptions = [];
|
||||
for (const item of actor.items) {
|
||||
if (item.system.metadata.hasActions) {
|
||||
const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])];
|
||||
for (const action of actions) {
|
||||
if (action.hasRoll) {
|
||||
const actionItem = {
|
||||
value: action.uuid,
|
||||
label: action.name,
|
||||
group: item.name,
|
||||
baseAction: action.baseAction
|
||||
};
|
||||
|
||||
if (action.hasDamage) damageRollOptions.push(actionItem);
|
||||
else rollOptions.push(actionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll
|
||||
? undefined
|
||||
: (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
|
||||
const damage = data.rollData?.options?.damage;
|
||||
partContext.hasDamage |= Boolean(damage);
|
||||
const critHitPointsDamage = await this.getCriticalDamage(damage);
|
||||
|
||||
partContext.members[partId] = {
|
||||
...data,
|
||||
roll: data.roll,
|
||||
isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: partId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
hasRolled: Boolean(data.rollData),
|
||||
rollOptions,
|
||||
damageRollOptions,
|
||||
damage: damage,
|
||||
critDamage: critHitPointsDamage,
|
||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
||||
};
|
||||
const data = await this.#prepareMemberContext(partId);
|
||||
partContext.hasDamage |= Boolean(data?.damage);
|
||||
partContext.members[partId] = data;
|
||||
}
|
||||
|
||||
return partContext;
|
||||
}
|
||||
|
||||
async #prepareMemberContext(partId) {
|
||||
const data = this.party.system.tagTeam.members[partId] ?? {};
|
||||
const actor = game.actors.get(partId);
|
||||
if (!actor) console.error(`Failed to get actor ${partId}`);
|
||||
|
||||
const rollOptions = [];
|
||||
const damageRollOptions = [];
|
||||
for (const item of actor?.items ?? []) {
|
||||
if (!item.system.metadata.hasActions) continue;
|
||||
const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])];
|
||||
for (const action of actions) {
|
||||
if (action.hasRoll) {
|
||||
const collection = action.hasDamage ? damageRollOptions : rollOptions;
|
||||
collection.push({
|
||||
value: action.uuid,
|
||||
label: action.name,
|
||||
group: item.name,
|
||||
baseAction: action.baseAction
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
const damage = data.rollData?.options?.damage;
|
||||
|
||||
return {
|
||||
...data,
|
||||
roll: data.roll,
|
||||
isEditable: actor?.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: partId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
hasRolled: Boolean(data.rollData),
|
||||
rollOptions,
|
||||
damageRollOptions,
|
||||
damage: damage,
|
||||
critDamage: await this.getCriticalDamage(damage),
|
||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
||||
};
|
||||
}
|
||||
|
||||
getUpdatingParts(target) {
|
||||
const { initialization, rollSelection, result } = this.constructor.PARTS;
|
||||
const isInitialization = this.tabGroups.application === initialization.id;
|
||||
|
|
@ -273,13 +277,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
);
|
||||
}
|
||||
|
||||
getIsEditable() {
|
||||
return this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.tagTeam.members[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
}
|
||||
|
||||
tagTeamRefresh = ({ refreshType, action, parts }) => {
|
||||
if (refreshType !== RefreshType.TagTeamRoll) return;
|
||||
|
||||
|
|
@ -434,8 +431,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
if (!result) return;
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const rollData = result.messageRoll.toJSON();
|
||||
delete rollData.options.messageRoll;
|
||||
this.updatePartyData(
|
||||
|
|
@ -651,42 +646,50 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) {
|
||||
const memberValues = Object.values(this.party.system.tagTeam.members);
|
||||
const selectedRoll = memberValues.find(x => x.selected);
|
||||
let baseMainRoll = selectedRoll ?? memberValues[0];
|
||||
let baseSecondaryRoll = selectedRoll
|
||||
? memberValues.find(x => !x.selected)
|
||||
: memberValues.length > 1
|
||||
? memberValues[1]
|
||||
: null;
|
||||
try {
|
||||
const memberValues = Object.values(this.party.system.tagTeam.members);
|
||||
const selectedRoll = memberValues.find(x => x.selected);
|
||||
const baseMainRoll = selectedRoll ?? memberValues[0];
|
||||
const baseSecondaryRoll = selectedRoll
|
||||
? memberValues.find(x => !x.selected)
|
||||
: memberValues.length > 1
|
||||
? memberValues[1]
|
||||
: null;
|
||||
|
||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||
|
||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
||||
const systemData = mainRoll.rollData.options;
|
||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
||||
const systemData = mainRoll.rollData.options;
|
||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
||||
|
||||
if (secondaryRollData?.options.hasDamage) {
|
||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
||||
: secondaryRollData.options.damage;
|
||||
if (systemData.damage) {
|
||||
for (const key in secondaryDamage) {
|
||||
const damage = secondaryDamage[key];
|
||||
systemData.damage[key].formula = [systemData.damage[key].formula, damage.formula]
|
||||
.filter(x => x)
|
||||
.join(' + ');
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts.push(...damage.parts);
|
||||
if (secondaryRollData?.options.hasDamage) {
|
||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
||||
: secondaryRollData.options.damage;
|
||||
if (systemData.damage) {
|
||||
for (const [key, damage] of Object.entries(secondaryDamage ?? {})) {
|
||||
if (key in systemData.damage) {
|
||||
systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula]
|
||||
.filter(x => x)
|
||||
.join(' + ');
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts.push(...damage.parts);
|
||||
} else {
|
||||
systemData.damage[key] = damage;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
systemData.damage = secondaryDamage;
|
||||
}
|
||||
} else {
|
||||
systemData.damage = secondaryDamage;
|
||||
}
|
||||
}
|
||||
|
||||
return mainRoll;
|
||||
return mainRoll;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async #onCancelRoll(_event, _button, options = { confirm: true }) {
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ?? {}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export { default as ActionConfig } from './action-config.mjs';
|
|||
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||
export { default as CharacterSettings } from './character-settings.mjs';
|
||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||
export { default as NPCSettings } from './npc-settings.mjs';
|
||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
||||
*/
|
||||
static getChangeChoices() {
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty', 'DhNPC'];
|
||||
|
||||
const getAllLeaves = (root, group, parentPath = '') => {
|
||||
const leaves = [];
|
||||
|
|
|
|||
85
module/applications/sheets-configs/npc-settings.mjs
Normal file
85
module/applications/sheets-configs/npc-settings.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
export default class DHNPCSettings extends DHBaseActorSettings {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['npc-settings'],
|
||||
position: { width: 455, height: 'auto' },
|
||||
actions: {},
|
||||
dragDrop: [
|
||||
{ dragSelector: null, dropSelector: '.tab.features' },
|
||||
{ dragSelector: '.feature-item', dropSelector: null }
|
||||
]
|
||||
};
|
||||
|
||||
/**@override */
|
||||
static PARTS = {
|
||||
header: {
|
||||
id: 'header',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/header.hbs'
|
||||
},
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
details: {
|
||||
id: 'details',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/details.hbs'
|
||||
},
|
||||
features: {
|
||||
id: 'features',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'details' }, { id: 'features' }],
|
||||
initial: 'details',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = context.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
const featureItem = event.currentTarget.closest('.feature-item');
|
||||
|
||||
if (featureItem) {
|
||||
const feature = this.actor.items.get(featureItem.id);
|
||||
const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData = item.toObject();
|
||||
delete itemData._id;
|
||||
|
||||
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ export { default as Adversary } from './adversary.mjs';
|
|||
export { default as Character } from './character.mjs';
|
||||
export { default as Companion } from './companion.mjs';
|
||||
export { default as Environment } from './environment.mjs';
|
||||
export { default as NPC } from './npc.mjs';
|
||||
export { default as Party } from './party.mjs';
|
||||
|
|
|
|||
|
|
@ -31,6 +31,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
136
module/applications/sheets/actors/npc.mjs
Normal file
136
module/applications/sheets/actors/npc.mjs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
|
||||
export default class NPCSheet extends DHBaseActorSheet {
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['npc'],
|
||||
position: { width: 660, height: 600 },
|
||||
window: { resizable: true },
|
||||
actions: {},
|
||||
window: {
|
||||
resizable: true,
|
||||
controls: [
|
||||
{
|
||||
icon: 'fa-solid fa-signature',
|
||||
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
|
||||
action: 'editAttribution'
|
||||
}
|
||||
]
|
||||
},
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/npc/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/actors/npc/navigation.hbs' },
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/npc/features.hbs',
|
||||
scrollable: ['.feature-section']
|
||||
},
|
||||
notes: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/npc/notes.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'notes' }, { id: 'features' }],
|
||||
initial: 'notes',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
_prepareTabs(group) {
|
||||
const result = super._prepareTabs(group);
|
||||
if (group === 'primary') {
|
||||
result.features.empty = this.document.system.features.length === 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'features':
|
||||
await this._prepareFeaturesContext(context, options);
|
||||
break;
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
break;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Header part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareHeaderContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
context.description = await TextEditor.implementation.enrichHTML(system.description, {
|
||||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Features part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareFeaturesContext(context, _options) {
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = this.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Biography part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareNotesContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
const paths = {
|
||||
notes: 'notes'
|
||||
};
|
||||
|
||||
for (const [key, path] of Object.entries(paths)) {
|
||||
const value = foundry.utils.getProperty(system, path);
|
||||
context[key] = {
|
||||
field: system.schema.getField(path),
|
||||
value,
|
||||
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 }]
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
@ -103,6 +103,19 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
{
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollActionRoll',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
visible: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||
},
|
||||
callback: async li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||
message.update({ rolls: [reroll] });
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
|
|
@ -113,9 +126,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
: false;
|
||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||
},
|
||||
callback: li => {
|
||||
callback: async li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
|
||||
const update = await message.system.getRerolledDamage();
|
||||
message.update(update);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() ?? []);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}))
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import DhCharacter from './character.mjs';
|
||||
import DhCompanion from './companion.mjs';
|
||||
import DhAdversary from './adversary.mjs';
|
||||
import DhNPC from './npc.mjs';
|
||||
import DhEnvironment from './environment.mjs';
|
||||
import DhParty from './party.mjs';
|
||||
|
||||
export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment, DhParty };
|
||||
export { DhCharacter, DhCompanion, DhAdversary, DhNPC, DhEnvironment, DhParty };
|
||||
|
||||
export const config = {
|
||||
character: DhCharacter,
|
||||
companion: DhCompanion,
|
||||
adversary: DhAdversary,
|
||||
npc: DhNPC,
|
||||
environment: DhEnvironment,
|
||||
party: DhParty
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ import { ActionField } from '../fields/actionField.mjs';
|
|||
import { commonActorRules } from './base.mjs';
|
||||
import DhCreature from './creature.mjs';
|
||||
import { bonusField } from '../fields/actorField.mjs';
|
||||
import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpers/utils.mjs';
|
||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||
import { getTierAdjustedAdversary } from './tierAdjustment.mjs';
|
||||
|
||||
export default class DhpAdversary extends DhCreature {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
|
||||
|
|
@ -206,205 +205,6 @@ export default class DhpAdversary extends DhCreature {
|
|||
/** Returns source data for this actor adjusted to a new tier, which can be used to create a new actor. */
|
||||
adjustForTier(tier) {
|
||||
const source = this.parent.toObject(true);
|
||||
|
||||
/** @type {(2 | 3 | 4)[]} */
|
||||
const tiers = new Array(Math.abs(tier - this.tier))
|
||||
.fill(0)
|
||||
.map((_, idx) => idx + Math.min(tier, this.tier) + 1);
|
||||
if (tier < this.tier) tiers.reverse();
|
||||
const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard];
|
||||
const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] }));
|
||||
|
||||
// Apply simple tier changes
|
||||
const scale = tier > this.tier ? 1 : -1;
|
||||
for (const entry of tierEntries) {
|
||||
source.system.difficulty += scale * entry.difficulty;
|
||||
source.system.damageThresholds.major += scale * entry.majorThreshold;
|
||||
source.system.damageThresholds.severe += scale * entry.severeThreshold;
|
||||
source.system.resources.hitPoints.max += scale * entry.hp;
|
||||
source.system.resources.stress.max += scale * entry.stress;
|
||||
source.system.attack.roll.bonus += scale * entry.attack;
|
||||
}
|
||||
|
||||
// Get the mean and standard deviation of expected damage in the previous and new tier
|
||||
// The data we have is for attack scaling, but we reuse this for action scaling later
|
||||
const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic;
|
||||
const damageMeta = {
|
||||
currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] },
|
||||
newDamageRange: { tier, ...expectedDamageData[tier] },
|
||||
type: 'attack'
|
||||
};
|
||||
|
||||
// Update damage of base attack
|
||||
try {
|
||||
this.#adjustActionDamage(source.system.attack, damageMeta);
|
||||
} catch (err) {
|
||||
ui.notifications.warn('Failed to convert attack damage of adversary');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Update damage of each item action, making sure to also update the description if possible
|
||||
const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g;
|
||||
for (const item of source.items) {
|
||||
// Replace damage inlines with new formulas
|
||||
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;
|
||||
|
||||
try {
|
||||
const adjusted = this.#calculateAdjustedDamage(formula, { ...damageMeta, type: 'action' });
|
||||
const newFormula = [
|
||||
adjusted.diceQuantity ? `${adjusted.diceQuantity}d${adjusted.faces}` : null,
|
||||
adjusted.bonus
|
||||
]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
return match.replace(formula, newFormula);
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update damage in item actions
|
||||
// Parse damage, and convert all formula matches in the descriptions to the new damage
|
||||
for (const action of Object.values(item.system.actions)) {
|
||||
try {
|
||||
const result = this.#adjustActionDamage(action, { ...damageMeta, type: 'action' });
|
||||
if (!result) continue;
|
||||
|
||||
for (const { previousFormula, formula } of Object.values(result)) {
|
||||
const oldFormulaRegexp = new RegExp(
|
||||
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
||||
);
|
||||
item.system.description = item.system.description.replace(oldFormulaRegexp, formula);
|
||||
action.description = action.description.replace(oldFormulaRegexp, formula);
|
||||
}
|
||||
} catch (err) {
|
||||
ui.notifications.warn(`Failed to convert action damage for item ${item.name}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally set the tier of the source data, now that everything is complete
|
||||
source.system.tier = tier;
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a damage object to a new damage range
|
||||
* @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term
|
||||
* @throws error if the formula is the wrong type
|
||||
*/
|
||||
#calculateAdjustedDamage(formula, { currentDamageRange, newDamageRange, type }) {
|
||||
const terms = parseTermsFromSimpleFormula(formula);
|
||||
const flatTerms = terms.filter(t => t.diceQuantity === 0);
|
||||
const diceTerms = terms.filter(t => t.diceQuantity > 0);
|
||||
if (flatTerms.length > 1 || diceTerms.length > 1) {
|
||||
throw new Error('invalid formula for conversion');
|
||||
}
|
||||
const value = {
|
||||
...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }),
|
||||
bonus: flatTerms[0]?.bonus ?? 0
|
||||
};
|
||||
const previousExpected = calculateExpectedValue(value);
|
||||
if (previousExpected === 0) return value; // nothing to do
|
||||
|
||||
const dieSizes = [4, 6, 8, 10, 12, 20];
|
||||
const steps = newDamageRange.tier - currentDamageRange.tier;
|
||||
const increasing = steps > 0;
|
||||
const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation;
|
||||
const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation);
|
||||
|
||||
// If this was just a flat number, convert to the expected damage and exit
|
||||
if (value.diceQuantity === 0) {
|
||||
value.bonus = Math.round(expected);
|
||||
return value;
|
||||
}
|
||||
|
||||
const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1;
|
||||
const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 });
|
||||
|
||||
// Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die
|
||||
const baseOverages = Math.floor(value.bonus / getExpectedDie());
|
||||
|
||||
// Prestep. Change number of dice for attacks, bump up/down for actions
|
||||
// We never bump up to d20, though we might bump down from it
|
||||
if (type === 'attack') {
|
||||
const minimum = increasing ? value.diceQuantity : 0;
|
||||
value.diceQuantity = Math.max(minimum, newDamageRange.tier);
|
||||
} else {
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)];
|
||||
}
|
||||
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
|
||||
// Attempt to handle negative values.
|
||||
// If we can do it with only step downs, do so. Otherwise remove tier dice, and try again
|
||||
if (value.bonus < 0) {
|
||||
let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
|
||||
// If step downs alone don't suffice, change the flat modifier, then calculate steps required again
|
||||
// If this isn't sufficient, the result will be slightly off. This is unlikely to happen
|
||||
if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) {
|
||||
value.diceQuantity -= increasing ? 1 : Math.abs(steps);
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
if (value.bonus >= 0) return value; // complete
|
||||
}
|
||||
|
||||
stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)];
|
||||
value.bonus = Math.max(0, Math.round(expected - getBaseAverage()));
|
||||
}
|
||||
|
||||
// If value is really high, we add a number of dice based on the number of overages
|
||||
// This attempts to preserve a similar amount of variance when increasing an action
|
||||
const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages;
|
||||
if (type !== 'attack' && increasing && overagesToRemove > 0) {
|
||||
value.diceQuantity += overagesToRemove;
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates damage to reflect a specific value.
|
||||
* @throws if damage structure is invalid for conversion
|
||||
* @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage
|
||||
*/
|
||||
#adjustActionDamage(action, damageMeta) {
|
||||
if (!action.damage?.parts.hitPoints) return null;
|
||||
|
||||
const result = {};
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const data = action.damage.parts.hitPoints[property];
|
||||
const previousFormula = data.custom.enabled
|
||||
? data.custom.formula
|
||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
const value = this.#calculateAdjustedDamage(previousFormula, damageMeta);
|
||||
const formula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
if (value.diceQuantity) {
|
||||
data.custom.enabled = false;
|
||||
data.bonus = value.bonus;
|
||||
data.dice = `d${value.faces}`;
|
||||
data.flatMultiplier = value.diceQuantity;
|
||||
} else if (!value.diceQuantity) {
|
||||
data.custom.enabled = true;
|
||||
data.custom.formula = formula;
|
||||
}
|
||||
|
||||
result[property] = { previousFormula, formula, value };
|
||||
}
|
||||
|
||||
return result;
|
||||
return getTierAdjustedAdversary(source, tier);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
43
module/data/actor/npc.mjs
Normal file
43
module/data/actor/npc.mjs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import DHNPCSettings from '../../applications/sheets-configs/npc-settings.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
export default class DhpNPC extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.NPC'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.npc',
|
||||
type: 'npc',
|
||||
settingSheet: DHNPCSettings,
|
||||
hasResistances: false,
|
||||
hasAttribution: true
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
difficulty: new fields.NumberField({
|
||||
nullable: true,
|
||||
initial: null,
|
||||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.difficulty'
|
||||
}),
|
||||
description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' }),
|
||||
motives: new fields.StringField(),
|
||||
notes: new fields.HTMLField()
|
||||
};
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_ICON = 'systems/daggerheart/assets/icons/documents/actors/drama-masks.svg';
|
||||
|
||||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
|
||||
isItemValid(source) {
|
||||
return super.isItemValid(source) || source.type === 'feature';
|
||||
}
|
||||
}
|
||||
219
module/data/actor/tierAdjustment.mjs
Normal file
219
module/data/actor/tierAdjustment.mjs
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
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;
|
||||
|
||||
/** @type {(2 | 3 | 4)[]} */
|
||||
const tiers = new Array(Math.abs(tier - currentTier))
|
||||
.fill(0)
|
||||
.map((_, idx) => idx + Math.min(tier, currentTier) + 1);
|
||||
if (tier < currentTier) tiers.reverse();
|
||||
const typeData = adversaryScalingData[source.system.type] ?? adversaryScalingData[source.system.standard];
|
||||
const tierEntries = tiers.map(t => ({ tier: t, ...typeData[t] }));
|
||||
|
||||
// Apply simple tier changes
|
||||
const scale = tier > currentTier ? 1 : -1;
|
||||
for (const entry of tierEntries) {
|
||||
source.system.difficulty += scale * entry.difficulty;
|
||||
source.system.damageThresholds.major += scale * entry.majorThreshold;
|
||||
source.system.damageThresholds.severe += scale * entry.severeThreshold;
|
||||
source.system.resources.hitPoints.max += scale * entry.hp;
|
||||
source.system.resources.stress.max += scale * entry.stress;
|
||||
source.system.attack.roll.bonus += scale * entry.attack;
|
||||
}
|
||||
|
||||
// Get the mean and standard deviation of expected damage in the previous and new tier
|
||||
// The data we have is for attack scaling, but we reuse this for action scaling later
|
||||
const expectedDamageData = adversaryExpectedDamage[source.system.type] ?? adversaryExpectedDamage.basic;
|
||||
const damageMeta = {
|
||||
currentDamageRange: { tier: source.system.tier, ...expectedDamageData[source.system.tier] },
|
||||
newDamageRange: { tier, ...expectedDamageData[tier] }
|
||||
};
|
||||
|
||||
// Store initial attack damage for abilities that have you deal a "standard attack"
|
||||
const initialAttack = {
|
||||
type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(),
|
||||
value: getDamagePartsFormula(source.system.attack.damage?.parts.hitPoints?.value)
|
||||
};
|
||||
|
||||
// Update damage of base attack.
|
||||
try {
|
||||
const damage = source.system.attack.damage;
|
||||
if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary');
|
||||
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const data = damage.parts.hitPoints[property];
|
||||
const previousFormula = getDamagePartsFormula(data);
|
||||
const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
|
||||
applyAdjustedDamage(data, value, formula);
|
||||
}
|
||||
} catch (err) {
|
||||
ui.notifications.warn('Failed to convert attack damage of adversary');
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
// Update damage of each item action, making sure to also update the description if possible
|
||||
const damageRegex = /@Damage\[([^\[\]]*)\]({[^}]*})?/g;
|
||||
for (const item of source.items) {
|
||||
// Replace damage inlines with new formulas. Keep a record for a specific check later
|
||||
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, { first: 'value' });
|
||||
if (!formula) return match;
|
||||
|
||||
try {
|
||||
const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula;
|
||||
descriptionFormulas.push(formula);
|
||||
return match.replace(formula, newFormula);
|
||||
} catch {
|
||||
return match;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update damage in item actions and convert all formula matches in the descriptions to the new damage
|
||||
for (const action of Object.values(item.system.actions)) {
|
||||
if (!action.damage?.parts.hitPoints) continue;
|
||||
try {
|
||||
// Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
|
||||
const result = [];
|
||||
for (const property of ['value', 'valueAlt']) {
|
||||
const { [property]: data, type: damageType } = action.damage.parts.hitPoints;
|
||||
const previousFormula = getDamagePartsFormula(data);
|
||||
const isActuallyAttack =
|
||||
previousFormula === initialAttack.value &&
|
||||
foundry.utils.equals(damageType.toSorted(), initialAttack.type) &&
|
||||
!descriptionFormulas.includes(previousFormula);
|
||||
const type = isActuallyAttack ? 'attack' : 'action';
|
||||
const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta);
|
||||
applyAdjustedDamage(data, value, formula);
|
||||
result.push({ previousFormula, formula });
|
||||
}
|
||||
|
||||
// Override text in the description with those values
|
||||
for (const { previousFormula, formula } of Object.values(result)) {
|
||||
const oldFormulaRegexp = new RegExp(
|
||||
previousFormula.replace(' ', '').replace('+', '(?:\\s)?\\+(?:\\s)?')
|
||||
);
|
||||
item.system.description = item.system.description.replace(oldFormulaRegexp, formula);
|
||||
action.description = action.description.replace(oldFormulaRegexp, formula);
|
||||
}
|
||||
} catch (err) {
|
||||
ui.notifications.warn(`Failed to convert action damage for item ${item.name}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Finally set the tier of the source data, now that everything is complete
|
||||
source.system.tier = tier;
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a damage object to a new damage range
|
||||
* @returns {{ diceQuantity: number; faces: number; bonus: number }} the adjusted result as a combined term
|
||||
* @throws error if the formula is the wrong type
|
||||
*/
|
||||
function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageRange }) {
|
||||
const terms = parseTermsFromSimpleFormula(formula);
|
||||
const flatTerms = terms.filter(t => t.diceQuantity === 0);
|
||||
const diceTerms = terms.filter(t => t.diceQuantity > 0);
|
||||
if (flatTerms.length > 1 || diceTerms.length > 1) {
|
||||
throw new Error('invalid formula for conversion');
|
||||
}
|
||||
const value = {
|
||||
...(diceTerms[0] ?? { diceQuantity: 0, faces: 1 }),
|
||||
bonus: flatTerms[0]?.bonus ?? 0
|
||||
};
|
||||
const previousExpected = calculateExpectedValue(value);
|
||||
if (previousExpected === 0) return value; // nothing to do
|
||||
|
||||
const dieSizes = [4, 6, 8, 10, 12, 20];
|
||||
const steps = newDamageRange.tier - currentDamageRange.tier;
|
||||
const increasing = steps > 0;
|
||||
const deviation = (previousExpected - currentDamageRange.mean) / currentDamageRange.deviation;
|
||||
const expected = Math.max(1, newDamageRange.mean + newDamageRange.deviation * deviation);
|
||||
|
||||
// If this was just a flat number, convert to the expected damage and exit
|
||||
if (value.diceQuantity === 0) {
|
||||
value.bonus = Math.round(expected);
|
||||
return value;
|
||||
}
|
||||
|
||||
const getExpectedDie = () => calculateExpectedValue({ diceQuantity: 1, faces: value.faces }) || 1;
|
||||
const getBaseAverage = () => calculateExpectedValue({ ...value, bonus: 0 });
|
||||
|
||||
// Check the number of base overages over the expected die. In the end, if the bonus inflates too much, we add a die
|
||||
const baseOverages = Math.floor(value.bonus / getExpectedDie());
|
||||
|
||||
// Prestep. Change number of dice for attacks, bump up/down for actions
|
||||
// We never bump up to d20, though we might bump down from it
|
||||
if (type === 'attack') {
|
||||
const minimum = increasing ? value.diceQuantity : 0;
|
||||
value.diceQuantity = Math.max(minimum, newDamageRange.tier);
|
||||
} else {
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
value.faces = dieSizes[Math.clamp(currentIdx + steps, 0, 4)];
|
||||
}
|
||||
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
|
||||
// Attempt to handle negative values.
|
||||
// If we can do it with only step downs, do so. Otherwise remove tier dice, and try again
|
||||
if (value.bonus < 0) {
|
||||
let stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
const currentIdx = dieSizes.indexOf(value.faces);
|
||||
|
||||
// If step downs alone don't suffice, change the flat modifier, then calculate steps required again
|
||||
// If this isn't sufficient, the result will be slightly off. This is unlikely to happen
|
||||
if (type !== 'attack' && stepsRequired > currentIdx && value.diceQuantity > 0) {
|
||||
value.diceQuantity -= increasing ? 1 : Math.abs(steps);
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
if (value.bonus >= 0) return value; // complete
|
||||
}
|
||||
|
||||
stepsRequired = Math.ceil(Math.abs(value.bonus) / value.diceQuantity);
|
||||
value.faces = dieSizes[Math.max(0, currentIdx - stepsRequired)];
|
||||
value.bonus = Math.max(0, Math.round(expected - getBaseAverage()));
|
||||
}
|
||||
|
||||
// If value is really high, we add a number of dice based on the number of overages
|
||||
// This attempts to preserve a similar amount of variance when increasing an action
|
||||
const overagesToRemove = Math.floor(value.bonus / getExpectedDie()) - baseOverages;
|
||||
if (type !== 'attack' && increasing && overagesToRemove > 0) {
|
||||
value.diceQuantity += overagesToRemove;
|
||||
value.bonus = Math.round(expected - getBaseAverage());
|
||||
}
|
||||
|
||||
const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus]
|
||||
.filter(p => !!p)
|
||||
.join('+');
|
||||
return { value, formula: newFormula };
|
||||
}
|
||||
|
||||
function getDamagePartsFormula(data) {
|
||||
return data.custom.enabled
|
||||
? data.custom.formula
|
||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates damage to reflect a specific value.
|
||||
* @throws if damage structure is invalid for conversion
|
||||
* @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage
|
||||
*/
|
||||
function applyAdjustedDamage(diceData, value, formula) {
|
||||
if (value.diceQuantity) {
|
||||
diceData.custom.enabled = false;
|
||||
diceData.bonus = value.bonus;
|
||||
diceData.dice = `d${value.faces}`;
|
||||
diceData.flatMultiplier = value.diceQuantity;
|
||||
} else if (!value.diceQuantity) {
|
||||
diceData.custom.enabled = true;
|
||||
diceData.custom.formula = formula;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
const targetsField = () =>
|
||||
|
|
@ -130,6 +132,35 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
});
|
||||
}
|
||||
|
||||
/* TODO: Change how damage data is stored somehow to enable better rerolling */
|
||||
async getRerolledDamage() {
|
||||
if (!this.damage) return;
|
||||
|
||||
const rerolls = [];
|
||||
const update = { system: { damage: {} } };
|
||||
for (const partKey in this.damage) {
|
||||
const part = this.damage[partKey];
|
||||
const testRoll = Roll.fromData(part.parts[0].roll);
|
||||
const rerolled = await testRoll.reroll();
|
||||
rerolls.push(rerolled);
|
||||
|
||||
if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] };
|
||||
const partData = update.system.damage[partKey].parts[0];
|
||||
update.system.damage[partKey].total = rerolled.total;
|
||||
partData.modifierTotal = rerolled.terms.reduce((acc, x) => {
|
||||
if (x.isDeterministic && !x.operator) acc += x.total;
|
||||
return acc;
|
||||
}, 0);
|
||||
partData.dice = rerolled.dice.map(d => ({ ...d.toJSON(), dice: d.denomination }));
|
||||
partData.total = rerolled.total;
|
||||
partData.roll = rerolled.toJSON();
|
||||
}
|
||||
|
||||
await triggerChatRollFx(rerolls);
|
||||
|
||||
return update;
|
||||
}
|
||||
|
||||
registerTargetHook() {
|
||||
if (!this.parent.isAuthor || !this.hasTarget) return;
|
||||
if (this.targetMode && this.parent.targetHook !== null) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -72,9 +72,6 @@ export default class DamageField extends fields.SchemaField {
|
|||
damageConfig.source.message = messageId;
|
||||
damageConfig.directDamage = !!damageConfig.source?.message;
|
||||
|
||||
// if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active)
|
||||
// await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message);
|
||||
|
||||
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
|
||||
if (!damageResult) return false;
|
||||
if (damageResult.actionChatMessageHandled) config.actionChatMessageHandled = true;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { emitGMUpdate, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class EffectsField extends fields.ArrayField {
|
||||
|
|
@ -34,8 +32,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
}
|
||||
if (EffectsField.getAutomation() || force) {
|
||||
targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit);
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid);
|
||||
// EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
|
||||
EffectsField.applyEffects.call(this, targets);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -59,16 +56,16 @@ export default class EffectsField extends fields.ArrayField {
|
|||
if (!token) return;
|
||||
|
||||
const messageToken = token.document ?? token;
|
||||
const conditionImmunities = messageToken.actor.system.rules.conditionImmunities ?? {};
|
||||
const conditionImmunities = messageToken.actor.system.rules?.conditionImmunities ?? {};
|
||||
messageTargets.push({
|
||||
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
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { itemAbleRollParse } from '../../../helpers/utils.mjs';
|
||||
import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs';
|
||||
import FormulaField from '../formulaField.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -40,7 +40,7 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
||||
await roll.evaluate();
|
||||
const count = roll.total;
|
||||
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||
if (!roll.isDeterministic) rolls.push(roll);
|
||||
|
||||
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||
|
|
@ -56,7 +56,7 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
}
|
||||
}
|
||||
|
||||
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
|
||||
if (rolls.length) await triggerChatRollFx(rolls);
|
||||
|
||||
this.actor.sheet?.minimize();
|
||||
DHSummonField.handleSummon(summonData, this.actor);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs';
|
|||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||
import { addLinkedItemsDiff, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
import { addLinkedItemsDiff, fromUuids, getFeaturesHTMLData, updateLinkedItemApps } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHClass extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -73,15 +73,16 @@ export default class DHClass extends BaseDataItem {
|
|||
const uuids = [this.parent.uuid, this.parent._stats?.compendiumSource].filter(u => !!u);
|
||||
const subclasses = game.items.filter(x => x.type === 'subclass' && uuids.includes(x.system.linkedClass));
|
||||
for (const pack of game.packs) {
|
||||
const packIds = [];
|
||||
const indexes = await pack.getIndex({ fields: ['system.linkedClass'] });
|
||||
for (const index of indexes) {
|
||||
if (index.type !== 'subclass') continue;
|
||||
if (!uuids.includes(index.system?.linkedClass)) continue;
|
||||
if (subclasses.find(x => x.uuid === index.uuid)) continue;
|
||||
|
||||
const subclass = await foundry.utils.fromUuid(index.uuid);
|
||||
subclasses.push(subclass);
|
||||
packIds.push(index._id);
|
||||
}
|
||||
|
||||
if (packIds.length > 0) subclasses.push(...(await pack.getDocuments({ _id__in: packIds })));
|
||||
}
|
||||
|
||||
return subclasses;
|
||||
|
|
@ -216,6 +217,10 @@ export default class DHClass extends BaseDataItem {
|
|||
classItems.push(contentLink.outerHTML);
|
||||
}
|
||||
|
||||
// Preload all class 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 hopeFeatures = await getFeaturesHTMLData(this.hopeFeatures);
|
||||
const classFeatures = await getFeaturesHTMLData(this.classFeatures);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import { fromUuids, getFeaturesHTMLData } from '../../helpers/utils.mjs';
|
||||
import ItemLinkFields from '../fields/itemLinkFields.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
|
|
@ -91,6 +90,11 @@ export default class DHSubclass extends BaseDataItem {
|
|||
const spellcastTrait = this.spellcastingTrait
|
||||
? game.i18n.localize(CONFIG.DH.ACTOR.abilities[this.spellcastingTrait].label)
|
||||
: null;
|
||||
|
||||
// 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));
|
||||
|
||||
const foundationFeatures = await getFeaturesHTMLData(this.foundationFeatures);
|
||||
const specializationFeatures = await getFeaturesHTMLData(this.specializationFeatures);
|
||||
const masteryFeatures = await getFeaturesHTMLData(this.masteryFeatures);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import { triggerChatRollFx } from '../helpers/utils.mjs';
|
||||
import DHRoll from './dhRoll.mjs';
|
||||
|
||||
export default class D20Roll extends DHRoll {
|
||||
|
|
@ -224,4 +225,15 @@ export default class D20Roll extends DHRoll {
|
|||
resetFormula() {
|
||||
return (this._formula = this.constructor.getFormula(this.terms));
|
||||
}
|
||||
|
||||
async reroll(options) {
|
||||
const result = await super.reroll(options);
|
||||
if (this instanceof game.system.api.dice.DualityRoll) return result;
|
||||
|
||||
if (options?.liveRoll) {
|
||||
await triggerChatRollFx([result]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import DamageDialog from '../applications/dialogs/damageDialog.mjs';
|
||||
import { parseRallyDice } from '../helpers/utils.mjs';
|
||||
import { parseRallyDice, triggerChatRollFx } from '../helpers/utils.mjs';
|
||||
import DHRoll from './dhRoll.mjs';
|
||||
|
||||
export default class DamageRoll extends DHRoll {
|
||||
|
|
@ -18,7 +18,12 @@ export default class DamageRoll extends DHRoll {
|
|||
if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate();
|
||||
|
||||
roll._evaluated = true;
|
||||
const parts = config.roll.map(r => this.postEvaluate(r));
|
||||
|
||||
const parts = [];
|
||||
for (const roll of config.roll) {
|
||||
parts.push(this.postEvaluate(roll));
|
||||
roll.roll = JSON.stringify(roll.roll.toJSON());
|
||||
}
|
||||
|
||||
config.damage = this.unifyDamageRoll(parts);
|
||||
}
|
||||
|
|
@ -38,25 +43,24 @@ export default class DamageRoll extends DHRoll {
|
|||
const chatMessage = config.source?.message
|
||||
? ui.chat.collection.get(config.source.message)
|
||||
: getDocumentClass('ChatMessage').applyMode({}, config.rollMode ?? 'public');
|
||||
|
||||
const diceRolls = [];
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||
),
|
||||
diceRoll = Roll.fromTerms([pool]);
|
||||
await game.dice3d.showForRoll(
|
||||
diceRoll,
|
||||
game.user,
|
||||
true,
|
||||
chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
||||
chatMessage.blind
|
||||
);
|
||||
config.mute = true;
|
||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||
);
|
||||
diceRolls.push(Roll.fromTerms([pool]));
|
||||
}
|
||||
|
||||
await triggerChatRollFx(diceRolls, {
|
||||
whisper: chatMessage.whisper?.length > 0 ? chatMessage.whisper : null,
|
||||
blind: chatMessage.blind
|
||||
});
|
||||
await super.buildPost(roll, config, message);
|
||||
|
||||
if (config.source?.message) {
|
||||
chatMessage.update({ 'system.damage': config.damage });
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,9 +323,10 @@ export default class DamageRoll extends DHRoll {
|
|||
const newIndex = parsedDiceTerms[dice].results.length;
|
||||
await term.reroll(`/r1=${termResult.result}`);
|
||||
|
||||
const diceRolls = [];
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const newResult = parsedDiceTerms[dice].results[newIndex];
|
||||
const diceSoNiceRoll = {
|
||||
diceRolls.push({
|
||||
_evaluated: true,
|
||||
dice: [
|
||||
new foundry.dice.terms.Die({
|
||||
|
|
@ -332,11 +337,10 @@ export default class DamageRoll extends DHRoll {
|
|||
})
|
||||
],
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
});
|
||||
}
|
||||
|
||||
await triggerChatRollFx(diceRolls);
|
||||
await parsedRoll.evaluate();
|
||||
|
||||
const results = parsedRoll.dice[dice].results.map(result => ({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import { triggerChatRollFx } from '../helpers/utils.mjs';
|
||||
|
||||
export default class DHRoll extends Roll {
|
||||
baseTerms = [];
|
||||
|
|
@ -36,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;
|
||||
|
|
@ -75,9 +77,7 @@ export default class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
if (config.skips?.createMessage) {
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
await game.dice3d.showForRoll(roll, game.user, true);
|
||||
}
|
||||
await triggerChatRollFx([roll]);
|
||||
} else if (!config.source?.message) {
|
||||
config.message = await this.toMessage(roll, config);
|
||||
}
|
||||
|
|
@ -85,6 +85,7 @@ export default class DHRoll extends Roll {
|
|||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
return {
|
||||
...roll.options.roll,
|
||||
total: roll.total,
|
||||
formula: roll.formula,
|
||||
dice: roll.dice.map(d => ({
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
import { updateResourcesForDualityReroll } from '../helpers.mjs';
|
||||
|
||||
export default class DualityDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
|
|
@ -12,24 +12,6 @@ export default class DualityDie extends foundry.dice.terms.Die {
|
|||
return roll.withHope ? 1 : roll.withFear ? -1 : 0;
|
||||
}
|
||||
|
||||
#updateResources(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;
|
||||
|
||||
const updates = [];
|
||||
const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0);
|
||||
const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0);
|
||||
const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0);
|
||||
|
||||
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
|
||||
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
|
||||
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
|
||||
|
||||
const resourceUpdates = new ResourceUpdateMap(actor);
|
||||
resourceUpdates.addResources(updates);
|
||||
resourceUpdates.updateResources();
|
||||
}
|
||||
|
||||
async reroll(modifier, options) {
|
||||
const oldDuality = this.#getDualityState(options.liveRoll.roll);
|
||||
await super.reroll(modifier, options);
|
||||
|
|
@ -57,7 +39,7 @@ export default class DualityDie extends foundry.dice.terms.Die {
|
|||
if (options.liveRoll.isReaction) return;
|
||||
|
||||
const newDuality = this.#getDualityState(options.liveRoll.roll);
|
||||
this.#updateResources(oldDuality, newDuality, options.liveRoll.actor);
|
||||
updateResourcesForDualityReroll(oldDuality, newDuality, options.liveRoll.actor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { updateResourcesForDualityReroll } from './helpers.mjs';
|
||||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageNumber = 1;
|
||||
|
|
@ -107,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);
|
||||
}
|
||||
|
|
@ -130,20 +132,14 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
createBaseDice() {
|
||||
if (
|
||||
this.dice[0] instanceof game.system.api.dice.diceTypes.HopeDie &&
|
||||
this.dice[1] instanceof game.system.api.dice.diceTypes.FearDie
|
||||
) {
|
||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||
return;
|
||||
}
|
||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||
|
||||
this.terms[0] = new game.system.api.dice.diceTypes.HopeDie({
|
||||
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
||||
faces: this.terms[0]?.faces ?? this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
||||
});
|
||||
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
||||
this.terms[2] = new game.system.api.dice.diceTypes.FearDie({
|
||||
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||
faces: this.terms[2]?.faces ?? this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -151,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) {
|
||||
|
|
@ -388,4 +384,40 @@ export default class DualityRoll extends D20Roll {
|
|||
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
||||
}
|
||||
}
|
||||
|
||||
async reroll(options) {
|
||||
const oldDuality = this.withHope ? 1 : this.withFear ? -1 : 0;
|
||||
const rerolled = DualityRoll.fromData((await super.reroll(options)).toJSON());
|
||||
|
||||
if (options?.liveRoll) {
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceAppearance = await getDiceSoNicePresets(
|
||||
rerolled,
|
||||
rerolled.dHope.denomination,
|
||||
rerolled.dFear.denomination
|
||||
);
|
||||
rerolled.dHope.options.appearance = diceAppearance.hope.appearance;
|
||||
rerolled.dFear.options.appearance = diceAppearance.fear.appearance;
|
||||
if (rerolled.dAdvantage) rerolled.dAdvantage.options.appearance = diceAppearance.advantage.appearance;
|
||||
if (rerolled.dDisadvantage)
|
||||
rerolled.dDisadvantage.options.appearance = diceAppearance.disadvantage.appearance;
|
||||
|
||||
await game.dice3d.showForRoll(rerolled, game.user, true);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
|
||||
if (this.options.actionType === 'reaction') return;
|
||||
|
||||
const newDuality = rerolled.withHope ? 1 : rerolled.withFear ? -1 : 0;
|
||||
const actor = await foundry.utils.fromUuid(this.options.source.actor);
|
||||
updateResourcesForDualityReroll(oldDuality, newDuality, actor);
|
||||
}
|
||||
|
||||
return rerolled;
|
||||
}
|
||||
|
||||
fromJSON(json) {
|
||||
return super.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
module/dice/helpers.mjs
Normal file
19
module/dice/helpers.mjs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
|
||||
const updates = [];
|
||||
const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0);
|
||||
const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0);
|
||||
const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0);
|
||||
|
||||
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
|
||||
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
|
||||
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
|
||||
|
||||
const resourceUpdates = new ResourceUpdateMap(actor);
|
||||
resourceUpdates.addResources(updates);
|
||||
resourceUpdates.updateResources();
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -109,6 +114,14 @@ export default class DhpActor extends Actor {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.type === 'npc') {
|
||||
Object.assign(update, {
|
||||
prototypeToken: {
|
||||
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.updateSource(update);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { AdversaryBPPerEncounter, BaseBPPerEncounter } from '../config/encounter
|
|||
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
|
||||
#wide = false;
|
||||
#bordered = false;
|
||||
#active = false;
|
||||
|
||||
async activate(element, options = {}) {
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { parseInlineParams } from './parser.mjs';
|
||||
|
||||
export default function DhDamageEnricher(match, _options) {
|
||||
const { value, type, inline } = parseInlineParams(match[1]);
|
||||
const { value, type, inline } = parseInlineParams(match[1], { first: 'value' });
|
||||
if (!value || !type) return match[0];
|
||||
return getDamageMessage(value, type, inline, match[0]);
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ export const renderDamageButton = async event => {
|
|||
{
|
||||
formula: value,
|
||||
applyTo: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
||||
type: type
|
||||
damageTypes: type
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 = `
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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(':');
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -864,3 +864,58 @@ export function camelize(str) {
|
|||
})
|
||||
.replace(/\s+/g, '');
|
||||
}
|
||||
|
||||
/** Bulk load a list of documents using uuids. Returns the documents in the same order */
|
||||
export async function fromUuids(uuids) {
|
||||
// Set up base entries. Each step works on a sublist of these objects
|
||||
const entries = uuids.map(uuid => ({
|
||||
uuid,
|
||||
parsed: foundry.utils.parseUuid(uuid),
|
||||
value: foundry.utils.fromUuidSync(uuid)
|
||||
}));
|
||||
|
||||
// Handle missing uuids for embedded documents first
|
||||
// A value may be index data, so we check if its a document
|
||||
const packEmbeddedEntries = entries.filter(
|
||||
e =>
|
||||
!(e.value instanceof Document) &&
|
||||
e.parsed &&
|
||||
e.parsed.collection instanceof foundry.documents.collections.CompendiumCollection &&
|
||||
e.parsed.embedded.length > 0
|
||||
);
|
||||
await Promise.all(
|
||||
packEmbeddedEntries.map(async e => {
|
||||
e.value = await fromUuid(e.uuid);
|
||||
return true;
|
||||
})
|
||||
);
|
||||
|
||||
// Handle missing top level pack stuff, by batching per pack
|
||||
const missingTopLevel = entries.filter(e => !(e.value instanceof Document) && e.value?.pack);
|
||||
for (const packGroup of Object.values(Object.groupBy(missingTopLevel, e => e.value.pack))) {
|
||||
const pack = game.packs.get(packGroup[0].value.pack);
|
||||
if (!pack) continue;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return entries.map(e => e.value);
|
||||
}
|
||||
/**
|
||||
* Triggers DiceSoNice rolls or dice roll audio for rolls. Not used for duality rolls.
|
||||
* @param { Roll[] } rolls
|
||||
* @return { void }
|
||||
*/
|
||||
export async function triggerChatRollFx(rolls, options = { whisper: false, blind: false }) {
|
||||
const { whisper, blind } = options;
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const rerollPromises = rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true, whisper, blind));
|
||||
await Promise.allSettled(rerollPromises);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
551
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
1
styles/less/dialog/actions/index.less
Normal file
1
styles/less/dialog/actions/index.less
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import "./action-list.less";
|
||||
1
styles/less/dialog/attribution/index.less
Normal file
1
styles/less/dialog/attribution/index.less
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import "./sheet.less";
|
||||
1
styles/less/dialog/beastform/index.less
Normal file
1
styles/less/dialog/beastform/index.less
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import "./sheet.less";
|
||||
4
styles/less/dialog/character-creation/index.less
Normal file
4
styles/less/dialog/character-creation/index.less
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@import "./sheet.less";
|
||||
@import "./creation-action-footer.less";
|
||||
@import "./selections-container.less";
|
||||
@import "./tab-navigation.less";
|
||||
1
styles/less/dialog/character-reset/index.less
Normal file
1
styles/less/dialog/character-reset/index.less
Normal file
|
|
@ -0,0 +1 @@
|
|||
@import './sheet.less';
|
||||
|
|
@ -0,0 +1 @@
|
|||
@import './sheet.less';
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue