mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-07-22 02:19:54 +02:00
Compare commits
55 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d50545af4e | ||
|
|
26bcc2dddc | ||
|
|
8d68166e4c | ||
|
|
3efdcb6c9a | ||
|
|
effab8db0c | ||
|
|
4b651836ff | ||
|
|
c181a47cc2 | ||
|
|
d76b4bb707 | ||
|
|
79d6522614 | ||
|
|
0c2d257871 | ||
|
|
4974df16d0 | ||
|
|
3a5529f1dc | ||
|
|
02a73d774a | ||
|
|
450287e4d0 | ||
|
|
81e264a477 | ||
|
|
d3d9ddfb41 | ||
|
|
7b35feb36d | ||
|
|
6e0d0b4e2c | ||
|
|
3de9c2f909 | ||
|
|
3faf588e6c | ||
|
|
783505da0a | ||
|
|
76ee777985 | ||
|
|
c6411ef0fe | ||
|
|
2c98070836 | ||
|
|
780aaf216c | ||
|
|
0fbfe388b0 | ||
|
|
7a01793f67 | ||
|
|
1e30ea42ba | ||
|
|
e55ef9fd9e | ||
|
|
6cd19ada8a | ||
|
|
b03772b633 | ||
|
|
b2eeed7bb7 | ||
|
|
2782b62793 | ||
|
|
4b5550a097 | ||
|
|
7e6fc03230 | ||
|
|
67b0d036a1 | ||
|
|
5889476b64 | ||
|
|
cb43ffed9b | ||
|
|
eccab6ad64 | ||
|
|
1dcfc92a03 | ||
|
|
98fedf3990 | ||
|
|
44dd3bd2e1 | ||
|
|
b3d96c25d9 | ||
|
|
53ed9fc792 | ||
|
|
e718c6b7b8 | ||
|
|
cdf6e7fdd0 | ||
|
|
d36c141d36 | ||
|
|
489f62a379 | ||
|
|
c25c4d63f7 | ||
|
|
1af86d87bb | ||
|
|
70388dbd73 | ||
|
|
2cc52fae1f | ||
|
|
8c6a470d84 | ||
|
|
9c58f7058e | ||
|
|
1ebbad4797 |
568 changed files with 7263 additions and 7792 deletions
14
README.md
14
README.md
|
|
@ -2,11 +2,17 @@
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Foundryborne Daggerheart](#foundryborne-daggerheart)
|
||||||
|
- [Table of Contents](#table-of-contents)
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [User Install Guide](#user-install)
|
- [User Install](#user-install)
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
- [Developer Setup](#development-setup)
|
- [Development Setup](#development-setup)
|
||||||
- [Contribution Info](#contributing)
|
- [Available Scripts](#available-scripts)
|
||||||
|
- [Notes](#notes)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [AI Policy](#ai-policy)
|
||||||
|
- [Disclaimer](#disclaimer)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
@ -72,7 +78,7 @@ Looking to contribute to the project? Look no further, check out our [contributi
|
||||||
|
|
||||||
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.
|
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:
|
## Disclaimer
|
||||||
|
|
||||||
**Daggerheart System**
|
**Daggerheart System**
|
||||||
Daggerheart is a trademark of Darrington Press LLC. All original content, mechanics, and intellectual property related to the Daggerheart roleplaying game are © Darrington Press LLC.
|
Daggerheart is a trademark of Darrington Press LLC. All original content, mechanics, and intellectual property related to the Daggerheart roleplaying game are © Darrington Press LLC.
|
||||||
|
|
|
||||||
36
daggerheart.d.ts
vendored
36
daggerheart.d.ts
vendored
|
|
@ -4,6 +4,18 @@ import '@common/primitives/global.mjs';
|
||||||
import Canvas from '@client/canvas/board.mjs';
|
import Canvas from '@client/canvas/board.mjs';
|
||||||
import { ResourceUpdateMap } from './module/data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from './module/data/action/baseAction.mjs';
|
||||||
|
|
||||||
|
import * as applications from './module/applications/_module.mjs';
|
||||||
|
import * as data from './module/data/_module.mjs';
|
||||||
|
import * as models from './module/data/_module.mjs';
|
||||||
|
import * as documents from './module/documents/_module.mjs';
|
||||||
|
import { macros } from './module/_module.mjs';
|
||||||
|
import * as dice from './module/dice/_module.mjs';
|
||||||
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
|
import { gameSettings } from './module/config/settingsConfig.mjs';
|
||||||
|
import DhCountdowns from './module/data/countdowns.mjs';
|
||||||
|
import DhAutomation from './module/data/settings/Automation.mjs';
|
||||||
|
|
||||||
|
|
||||||
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
|
// Foundry's use of `Object.assign(globalThis) means many globally available objects are not read as such
|
||||||
// This declare global hopefully fixes that
|
// 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
|
// Note: eslint is not aware of these, whatever is added here should go in the eslint's globals list
|
||||||
|
|
@ -80,3 +92,27 @@ declare global {
|
||||||
damageOptions: object;
|
damageOptions: object;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@client/packages/system.mjs' {
|
||||||
|
export default interface System {
|
||||||
|
api: {
|
||||||
|
applications: typeof applications,
|
||||||
|
data: typeof data,
|
||||||
|
models: typeof models,
|
||||||
|
documents: typeof documents,
|
||||||
|
macros: typeof macros,
|
||||||
|
dice: typeof dice,
|
||||||
|
fields: typeof fields
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@client/helpers/client-settings.mjs' {
|
||||||
|
// Add explicit typed overrides for auto complete. These require /** @type {"string"} on the vars themselves to work */
|
||||||
|
export default interface ClientSettings {
|
||||||
|
get(namespace: 'daggerheart', key: typeof gameSettings.Automation): DhAutomation;
|
||||||
|
get(namespace: 'daggerheart', key: typeof gameSettings.Homebrew): DhHomebrew;
|
||||||
|
get(namespace: 'daggerheart', key: typeof gameSettings.Countdowns): DhCountdowns;
|
||||||
|
get(namespace: 'daggerheart', key: string): unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import * as documents from './module/documents/_module.mjs';
|
||||||
import { macros } from './module/_module.mjs';
|
import { macros } from './module/_module.mjs';
|
||||||
import * as collections from './module/documents/collections/_module.mjs';
|
import * as collections from './module/documents/collections/_module.mjs';
|
||||||
import * as dice from './module/dice/_module.mjs';
|
import * as dice from './module/dice/_module.mjs';
|
||||||
|
import * as die from './module/dice/die/_module.mjs';
|
||||||
import * as fields from './module/data/fields/_module.mjs';
|
import * as fields from './module/data/fields/_module.mjs';
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
|
|
@ -23,7 +24,7 @@ import TokenManager from './module/documents/tokenManager.mjs';
|
||||||
CONFIG.DH = SYSTEM;
|
CONFIG.DH = SYSTEM;
|
||||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
|
|
||||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
|
CONFIG.Dice.rolls = [Roll = BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
|
||||||
CONFIG.Dice.daggerheart = {
|
CONFIG.Dice.daggerheart = {
|
||||||
DHRoll: DHRoll,
|
DHRoll: DHRoll,
|
||||||
DualityRoll: DualityRoll,
|
DualityRoll: DualityRoll,
|
||||||
|
|
@ -38,6 +39,8 @@ CONFIG.RegionBehavior.dataModels = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);
|
||||||
|
CONFIG.Dice.terms.d = die.BaseDie;
|
||||||
|
CONFIG.Dice.types = [die.BaseDie, CONFIG.Dice.terms.f];
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
|
|
@ -267,6 +270,10 @@ Hooks.on('i18nInit', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('setup', () => {
|
Hooks.on('setup', () => {
|
||||||
|
if (game.user.isGM) {
|
||||||
|
document.body.dataset.gm = true;
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG.statusEffects = [
|
CONFIG.statusEffects = [
|
||||||
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
|
||||||
...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
|
...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
|
||||||
|
|
@ -390,7 +397,7 @@ const updateActorsRangeDependentEffects = async token => {
|
||||||
).rangeMeasurement;
|
).rangeMeasurement;
|
||||||
|
|
||||||
for (let effect of token.actor?.allApplicableEffects() ?? []) {
|
for (let effect of token.actor?.allApplicableEffects() ?? []) {
|
||||||
if (!effect.system.rangeDependence?.enabled) continue;
|
if (!effect.system.rangeDependence) continue;
|
||||||
const { target, range, type } = effect.system.rangeDependence;
|
const { target, range, type } = effect.system.rangeDependence;
|
||||||
|
|
||||||
// If there are no targets, assume false. Otherwise, start with the effect enabled.
|
// If there are no targets, assume false. Otherwise, start with the effect enabled.
|
||||||
|
|
|
||||||
59
lang/en.json
59
lang/en.json
|
|
@ -84,6 +84,7 @@
|
||||||
"transformActorMissing": "The assigned actor to transform into does not exist. It was probably deleted or moved in/out of a compendium",
|
"transformActorMissing": "The assigned actor to transform into does not exist. It was probably deleted or moved in/out of a compendium",
|
||||||
"canvasError": "There is no active scene.",
|
"canvasError": "There is no active scene.",
|
||||||
"prototypeError": "You can only use a transform action from a Token",
|
"prototypeError": "You can only use a transform action from a Token",
|
||||||
|
"linkedSelectedError": "To transform a linked actor there either needs to be only a single token of it on the canvas, or you need to left-click select only one of them.",
|
||||||
"actorLinkError": "You cannot transform a token with Actor Link set to true"
|
"actorLinkError": "You cannot transform a token with Actor Link set to true"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -103,8 +104,10 @@
|
||||||
"startCountdown": "Start Countdown"
|
"startCountdown": "Start Countdown"
|
||||||
},
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
|
"addResource": "Add Resource",
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"flatMultiplier": "Flat Multiplier"
|
"flatMultiplier": "Flat Multiplier",
|
||||||
|
"markResources": "Mark Resources"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"customFormula": "Custom Formula",
|
"customFormula": "Custom Formula",
|
||||||
|
|
@ -113,7 +116,8 @@
|
||||||
"area": {
|
"area": {
|
||||||
"sectionTitle": "Areas",
|
"sectionTitle": "Areas",
|
||||||
"shape": "Shape",
|
"shape": "Shape",
|
||||||
"size": "Size"
|
"size": "Size",
|
||||||
|
"hasHole": "Area Hole"
|
||||||
},
|
},
|
||||||
"displayInChat": "Display in chat",
|
"displayInChat": "Display in chat",
|
||||||
"deleteTriggerTitle": "Delete Trigger",
|
"deleteTriggerTitle": "Delete Trigger",
|
||||||
|
|
@ -122,6 +126,10 @@
|
||||||
"damageOnSave": "Damage on Save",
|
"damageOnSave": "Damage on Save",
|
||||||
"useDefaultItemValues": "Use default Item values"
|
"useDefaultItemValues": "Use default Item values"
|
||||||
},
|
},
|
||||||
|
"Reload": {
|
||||||
|
"checkReload": "Check Reload",
|
||||||
|
"reloadRequired": "Reload Required!"
|
||||||
|
},
|
||||||
"RollField": {
|
"RollField": {
|
||||||
"diceRolling": {
|
"diceRolling": {
|
||||||
"compare": "Should be",
|
"compare": "Should be",
|
||||||
|
|
@ -165,9 +173,7 @@
|
||||||
},
|
},
|
||||||
"ACTIVEEFFECT": {
|
"ACTIVEEFFECT": {
|
||||||
"Config": {
|
"Config": {
|
||||||
"rangeDependence": {
|
"rangeDependence": { "title": "Range Dependence" },
|
||||||
"title": "Range Dependence"
|
|
||||||
},
|
|
||||||
"stacking": { "title": "Stacking" },
|
"stacking": { "title": "Stacking" },
|
||||||
"targetDispositions": "Affected Dispositions"
|
"targetDispositions": "Affected Dispositions"
|
||||||
},
|
},
|
||||||
|
|
@ -772,7 +778,7 @@
|
||||||
"removeRoll": "Remove Roll",
|
"removeRoll": "Remove Roll",
|
||||||
"resultsHint": "Results will appear when characters roll",
|
"resultsHint": "Results will appear when characters roll",
|
||||||
"selectLeaderHint": "Select one Character to be the leader",
|
"selectLeaderHint": "Select one Character to be the leader",
|
||||||
"selectParticipantsHint": "Select one Character to be the leader",
|
"selectParticipantsHint": "Select all Characters who will participate in the roll",
|
||||||
"startGroupRoll": "Start Group Roll",
|
"startGroupRoll": "Start Group Roll",
|
||||||
"title": "Group Roll"
|
"title": "Group Roll"
|
||||||
},
|
},
|
||||||
|
|
@ -870,6 +876,10 @@
|
||||||
"bruiser": "for each Bruiser adversary.",
|
"bruiser": "for each Bruiser adversary.",
|
||||||
"solo": "for each Solo adversary."
|
"solo": "for each Solo adversary."
|
||||||
},
|
},
|
||||||
|
"AreaTypes": {
|
||||||
|
"attached": { "label": "Attached To Token" },
|
||||||
|
"placed": { "label": "Placed In Scene" }
|
||||||
|
},
|
||||||
"ArmorInteraction": {
|
"ArmorInteraction": {
|
||||||
"none": { "label": "Ignores Armor" },
|
"none": { "label": "Ignores Armor" },
|
||||||
"active": { "label": "Active w/ Armor" },
|
"active": { "label": "Active w/ Armor" },
|
||||||
|
|
@ -1316,6 +1326,11 @@
|
||||||
"short": "V. Far"
|
"short": "V. Far"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ReloadChoices": {
|
||||||
|
"off": { "label": "Don't Use" },
|
||||||
|
"button": { "label": "Use button" },
|
||||||
|
"auto": { "label": "Automatic" }
|
||||||
|
},
|
||||||
"RollTypes": {
|
"RollTypes": {
|
||||||
"trait": {
|
"trait": {
|
||||||
"name": "Trait"
|
"name": "Trait"
|
||||||
|
|
@ -2215,7 +2230,9 @@
|
||||||
},
|
},
|
||||||
"Resource": {
|
"Resource": {
|
||||||
"single": "Resource",
|
"single": "Resource",
|
||||||
"plural": "Resources"
|
"plural": "Resources",
|
||||||
|
"unloaded": "The weapon is not loaded",
|
||||||
|
"loaded": "The weapon is loaded"
|
||||||
},
|
},
|
||||||
"Roll": {
|
"Roll": {
|
||||||
"attack": "Attack Roll",
|
"attack": "Attack Roll",
|
||||||
|
|
@ -2241,7 +2258,7 @@
|
||||||
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
"hint": "A used armor slot normally reduces damage by one step. This value increases the number of steps damage is reduced by."
|
||||||
},
|
},
|
||||||
"magical": {
|
"magical": {
|
||||||
"label": "Daamge Reduction: Only Magical",
|
"label": "Damage Reduction: Only Magical",
|
||||||
"hint": "Armor can only be used to reduce magical damage"
|
"hint": "Armor can only be used to reduce magical damage"
|
||||||
},
|
},
|
||||||
"maxArmorMarkedBonus": "Max Armor Used",
|
"maxArmorMarkedBonus": "Max Armor Used",
|
||||||
|
|
@ -2479,7 +2496,6 @@
|
||||||
"reroll": "Reroll",
|
"reroll": "Reroll",
|
||||||
"rerolled": "Rerolled",
|
"rerolled": "Rerolled",
|
||||||
"rerollThing": "Reroll {thing}",
|
"rerollThing": "Reroll {thing}",
|
||||||
"resource": "Resource",
|
|
||||||
"result": {
|
"result": {
|
||||||
"single": "Result",
|
"single": "Result",
|
||||||
"plural": "Results"
|
"plural": "Results"
|
||||||
|
|
@ -2546,6 +2562,9 @@
|
||||||
},
|
},
|
||||||
"identifier": {
|
"identifier": {
|
||||||
"label": "Identifier"
|
"label": "Identifier"
|
||||||
|
},
|
||||||
|
"gmNotes": {
|
||||||
|
"label": "GM Notes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Ancestry": {
|
"Ancestry": {
|
||||||
|
|
@ -2561,6 +2580,9 @@
|
||||||
"severe": "Severe Threshold"
|
"severe": "Severe Threshold"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Base": {
|
||||||
|
"addGMNote": "Add GM Note"
|
||||||
|
},
|
||||||
"Beastform": {
|
"Beastform": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"beastformType": { "label": "Beastform Type" },
|
"beastformType": { "label": "Beastform Type" },
|
||||||
|
|
@ -2677,6 +2699,9 @@
|
||||||
"displayFear": {
|
"displayFear": {
|
||||||
"label": "Display Fear"
|
"label": "Display Fear"
|
||||||
},
|
},
|
||||||
|
"fearPosition": {
|
||||||
|
"label": "Fear Position"
|
||||||
|
},
|
||||||
"displayCountdownUI": {
|
"displayCountdownUI": {
|
||||||
"label": "Display Countdown UI"
|
"label": "Display Countdown UI"
|
||||||
},
|
},
|
||||||
|
|
@ -2728,6 +2753,13 @@
|
||||||
"token": "Tokens",
|
"token": "Tokens",
|
||||||
"bar": "Bar",
|
"bar": "Bar",
|
||||||
"hide": "Hide"
|
"hide": "Hide"
|
||||||
|
},
|
||||||
|
"fearPosition": {
|
||||||
|
"free": "Free",
|
||||||
|
"topCenter": "Top + Center",
|
||||||
|
"bottomCenter": "Bottom + Center",
|
||||||
|
"rightTop": "Right + Top",
|
||||||
|
"leftBottom": "Left + Bottom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Automation": {
|
"Automation": {
|
||||||
|
|
@ -2786,6 +2818,10 @@
|
||||||
"hint": "Effects with defined range dependency will automatically turn on/off depending on range"
|
"hint": "Effects with defined range dependency will automatically turn on/off depending on range"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"reload": {
|
||||||
|
"label": "Reload Checking",
|
||||||
|
"hint": "If the system should present a button for checking if the character will need to reload or do it automatically"
|
||||||
|
},
|
||||||
"resourceScrollTexts": {
|
"resourceScrollTexts": {
|
||||||
"label": "Show Resource Change Scrolltexts",
|
"label": "Show Resource Change Scrolltexts",
|
||||||
"hint": "When a character is damaged, uses armor etc, a scrolling text will briefly appear by the token to signify this."
|
"hint": "When a character is damaged, uses armor etc, a scrolling text will briefly appear by the token to signify this."
|
||||||
|
|
@ -3229,7 +3265,7 @@
|
||||||
"beastformToManyAdvantages": "You cannot select any more advantages.",
|
"beastformToManyAdvantages": "You cannot select any more advantages.",
|
||||||
"beastformToManyFeatures": "You cannot select any more features.",
|
"beastformToManyFeatures": "You cannot select any more features.",
|
||||||
"beastformEquipWeapon": "You cannot use weapons while in a Beastform.",
|
"beastformEquipWeapon": "You cannot use weapons while in a Beastform.",
|
||||||
"loadoutMaxReached": "You've reached maximum loadout. Move atleast one domain card to the vault, or increase the limit in homebrew settings if desired.",
|
"loadoutMaxReached": "You've reached the maximum loadout size. To add more, move at least one domain card to the vault.",
|
||||||
"domainMaxReached": "You've reached the maximum domains for the class. Increase the limit in homebrew settings if desired.",
|
"domainMaxReached": "You've reached the maximum domains for the class. Increase the limit in homebrew settings if desired.",
|
||||||
"insufficientResources": "You don't have enough resources to use that action.",
|
"insufficientResources": "You don't have enough resources to use that action.",
|
||||||
"actionNoUsesRemaining": "That action doesn't have remaining uses.",
|
"actionNoUsesRemaining": "That action doesn't have remaining uses.",
|
||||||
|
|
@ -3247,7 +3283,8 @@
|
||||||
"knowTheTide": "Know The Tide gained a token",
|
"knowTheTide": "Know The Tide gained a token",
|
||||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||||
"noTokenTargeted": "No token is targeted",
|
"noTokenTargeted": "No token is targeted",
|
||||||
"behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM"
|
"behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM",
|
||||||
|
"reloadRequired": "The {weapon} must be reloaded to be used!"
|
||||||
},
|
},
|
||||||
"Progress": {
|
"Progress": {
|
||||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||||
|
|
|
||||||
|
|
@ -315,15 +315,15 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
|
|
||||||
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
|
const beastformEffect = selected.effects.find(x => x.type === 'beastform');
|
||||||
for (const traitBonus of app.modifications.traitBonuses) {
|
for (const traitBonus of app.modifications.traitBonuses) {
|
||||||
const existingChange = beastformEffect.changes.find(
|
const existingChange = beastformEffect.system.changes.find(
|
||||||
x => x.key === `system.traits.${traitBonus.trait}.value`
|
x => x.key === `system.traits.${traitBonus.trait}.value`
|
||||||
);
|
);
|
||||||
if (existingChange) {
|
if (existingChange) {
|
||||||
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
|
existingChange.value = Number.parseInt(existingChange.value) + traitBonus.bonus;
|
||||||
} else {
|
} else {
|
||||||
beastformEffect.changes.push({
|
beastformEffect.system.changes.push({
|
||||||
key: `system.traits.${traitBonus.trait}.value`,
|
key: `system.traits.${traitBonus.trait}.value`,
|
||||||
mode: 2,
|
type: 'add',
|
||||||
priority: null,
|
priority: null,
|
||||||
value: traitBonus.bonus
|
value: traitBonus.bonus
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.config = CONFIG.DH;
|
context.config = CONFIG.DH;
|
||||||
context.title = this.config.title ?? this.title;
|
context.title = this.config.title ?? this.title;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
|
||||||
|
const { damageFormula, resourceFormulas } = this.roll.constructFormulas(this.config);
|
||||||
|
context.damageFormula = damageFormula;
|
||||||
|
context.resourceFormulas = resourceFormulas;
|
||||||
|
|
||||||
context.hasHealing = this.config.hasHealing;
|
context.hasHealing = this.config.hasHealing;
|
||||||
context.directDamage = this.config.directDamage;
|
context.directDamage = this.config.directDamage;
|
||||||
context.selectedMessageMode = this.config.selectedMessageMode;
|
context.selectedMessageMode = this.config.selectedMessageMode;
|
||||||
|
|
@ -73,7 +77,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const data = foundry.utils.expandObject(formData.object);
|
const data = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, data.roll);
|
|
||||||
|
if (this.config.damageFormula)
|
||||||
|
foundry.utils.mergeObject(this.config.damageFormula, data.damageFormula);
|
||||||
|
|
||||||
|
foundry.utils.mergeObject(this.config.resourceFormulas, data.resourceFormulas);
|
||||||
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
foundry.utils.mergeObject(this.config.modifiers, data.modifiers);
|
||||||
this.config.selectedMessageMode = data.selectedMessageMode;
|
this.config.selectedMessageMode = data.selectedMessageMode;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,14 +139,16 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
|
name: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.name'),
|
||||||
description: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'),
|
description: game.i18n.localize('DAGGERHEART.CONFIG.DeathMoves.blazeOfGlory.description'),
|
||||||
img: CONFIG.DH.GENERAL.deathMoves.blazeOfGlory.img,
|
img: CONFIG.DH.GENERAL.deathMoves.blazeOfGlory.img,
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.rules.roll.guaranteedCritical',
|
key: 'system.rules.roll.guaranteedCritical',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: 'true'
|
value: 'true'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||||
|
|
@ -183,8 +185,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
|
|
||||||
if (result === undefined) return;
|
if (result === undefined) return;
|
||||||
|
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
@ -200,7 +200,6 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
||||||
img: this.selectedMove.img,
|
img: this.selectedMove.img,
|
||||||
description: game.i18n.localize(this.selectedMove.description),
|
description: game.i18n.localize(this.selectedMove.description),
|
||||||
result: result,
|
result: result,
|
||||||
open: autoExpandDescription ? 'open' : '',
|
|
||||||
showRiskItAllButton: this.showRiskItAllButton,
|
showRiskItAllButton: this.showRiskItAllButton,
|
||||||
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
riskItAllButtonLabel: this.riskItAllButtonLabel,
|
||||||
riskItAllHope: this.riskItAllHope
|
riskItAllHope: this.riskItAllHope
|
||||||
|
|
|
||||||
|
|
@ -196,9 +196,6 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||||
.filter(x => x.uuid !== this.actor.uuid);
|
.filter(x => x.uuid !== this.actor.uuid);
|
||||||
|
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const msg = {
|
const msg = {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
|
|
@ -219,8 +216,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
actor: { name: this.actor.name, img: this.actor.img },
|
actor: { name: this.actor.name, img: this.actor.img },
|
||||||
moves: moves,
|
moves: moves,
|
||||||
characters: characters,
|
characters: characters,
|
||||||
selfId: this.actor.uuid,
|
selfId: this.actor.uuid
|
||||||
open: autoExpandDescription ? 'open' : ''
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||||
import { shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
import { shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
||||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
import Party from '../sheets/actors/party.mjs';
|
import PartySheet from '../sheets/actors/party.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
|
|
||||||
this.party = party;
|
this.party = party;
|
||||||
this.partyMembers = party.system.partyMembers
|
this.partyMembers = party.system.partyMembers
|
||||||
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
.filter(x => PartySheet.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
.map(member => ({
|
.map(member => ({
|
||||||
...member.toObject(),
|
...member.toObject(),
|
||||||
uuid: member.uuid,
|
uuid: member.uuid,
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||||
|
import { ChatDamageData } from '../../data/chat-message/chatDamageData.mjs';
|
||||||
import { MemberData } from '../../data/tagTeamData.mjs';
|
import { MemberData } from '../../data/tagTeamData.mjs';
|
||||||
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
import { getCritDamageBonus, shouldUseHopeFearAutomation } from '../../helpers/utils.mjs';
|
||||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
import Party from '../sheets/actors/party.mjs';
|
import PartySheet from '../sheets/actors/party.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -13,7 +14,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.usesTagTeamHopeCost = true;
|
this.usesTagTeamHopeCost = true;
|
||||||
this.party = party;
|
this.party = party;
|
||||||
this.partyMembers = party.system.partyMembers
|
this.partyMembers = party.system.partyMembers
|
||||||
.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
.filter(x => PartySheet.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
.map(member => ({
|
.map(member => ({
|
||||||
...member.toObject(),
|
...member.toObject(),
|
||||||
uuid: member.uuid,
|
uuid: member.uuid,
|
||||||
|
|
@ -140,7 +141,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const hasRolled = Boolean(data.rollData);
|
const hasRolled = Boolean(data.rollData);
|
||||||
if (!hasRolled) return false;
|
if (!hasRolled) return false;
|
||||||
|
|
||||||
return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
|
return !data.rollData.options.hasDamage || data.damageRollData.active;
|
||||||
});
|
});
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -181,7 +182,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||||
const critSelected = !selectedRoll
|
const critSelected = !selectedRoll
|
||||||
? undefined
|
? undefined
|
||||||
: (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
: (selectedRoll?.roll?.isCritical ?? false);
|
||||||
|
|
||||||
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
|
partContext.hintText = await this.getInfoTexts(this.party.system.tagTeam.members);
|
||||||
partContext.joinedRoll = await this.getJoinedRoll({
|
partContext.joinedRoll = await this.getJoinedRoll({
|
||||||
|
|
@ -208,6 +209,16 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
const rollOptions = [];
|
const rollOptions = [];
|
||||||
const damageRollOptions = [];
|
const damageRollOptions = [];
|
||||||
|
|
||||||
|
if (actor?.system.usedUnarmed) {
|
||||||
|
damageRollOptions.push({
|
||||||
|
value: actor.system.attack.uuid,
|
||||||
|
label: actor.system.usedUnarmed.name,
|
||||||
|
group: actor.name,
|
||||||
|
baseAction: actor.system.attack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of actor?.items ?? []) {
|
for (const item of actor?.items ?? []) {
|
||||||
if (!item.system.metadata.hasActions) continue;
|
if (!item.system.metadata.hasActions) continue;
|
||||||
const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])];
|
const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])];
|
||||||
|
|
@ -225,8 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||||
const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
const critSelected = !selectedRoll ? undefined : (selectedRoll?.roll?.isCritical ?? false);
|
||||||
const damage = data.rollData?.options?.damage;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
|
|
@ -237,9 +247,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
hasRolled: Boolean(data.rollData),
|
hasRolled: Boolean(data.rollData),
|
||||||
rollOptions,
|
rollOptions,
|
||||||
damageRollOptions,
|
damageRollOptions,
|
||||||
damage: damage,
|
damage: data.damageRollData,
|
||||||
critDamage: await this.getCriticalDamage(damage),
|
critDamage: await this.getCriticalDamage(data.damageRollData),
|
||||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
useCritDamage: critSelected || (critSelected === undefined && data.roll?.isCritical)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,7 +379,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
let rollIsSelected = false;
|
let rollIsSelected = false;
|
||||||
for (const member of Object.values(members)) {
|
for (const member of Object.values(members)) {
|
||||||
const rollFinished = Boolean(member.rollData);
|
const rollFinished = Boolean(member.rollData);
|
||||||
const damageFinished = member.rollData?.options?.hasDamage ? Boolean(member.rollData.options.damage) : true;
|
const damageFinished = member.rollData?.options?.hasDamage ? member.damageRollData.active : true;
|
||||||
|
|
||||||
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
rollsAreFinished = rollsAreFinished && rollFinished && damageFinished;
|
||||||
rollIsSelected = rollIsSelected || member.selected;
|
rollIsSelected = rollIsSelected || member.selected;
|
||||||
|
|
@ -531,16 +541,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
await action.workflow.get('damage').execute(config, null, true);
|
await action.workflow.get('damage').execute(config, null, true);
|
||||||
if (!config.damage) return;
|
if (!config.damage) return;
|
||||||
|
|
||||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
|
||||||
await this.updatePartyData(
|
await this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
[`system.tagTeam.members.${memberKey}.damageRollData`]: config.damage
|
||||||
...current,
|
|
||||||
options: {
|
|
||||||
...current.options,
|
|
||||||
damage: config.damage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
);
|
);
|
||||||
|
|
@ -548,15 +551,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
static async #removeDamageRoll(_, button) {
|
static async #removeDamageRoll(_, button) {
|
||||||
const { memberKey } = button.dataset;
|
const { memberKey } = button.dataset;
|
||||||
const current = this.party.system.tagTeam.members[memberKey].rollData;
|
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.rollData`]: {
|
[`system.tagTeam.members.${memberKey}.damageRollData`]: {
|
||||||
...current,
|
main: null,
|
||||||
options: {
|
resources: _replace({})
|
||||||
...current.options,
|
|
||||||
damage: null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
|
|
@ -564,81 +563,35 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #rerollDamageDice(_, button) {
|
static async #rerollDamageDice(_, button) {
|
||||||
const { memberKey, damageKey, part, dice } = button.dataset;
|
const { isResource, memberKey, damageKey, diceIndex, resultIndex } = button.dataset;
|
||||||
const memberData = this.party.system.tagTeam.members[memberKey];
|
const memberData = this.party.system.tagTeam.members[memberKey];
|
||||||
const partData = memberData.rollData.options.damage[damageKey].parts[part];
|
await memberData.damageRollData.rerollDamageDie(isResource, damageKey, diceIndex, resultIndex);
|
||||||
const activeDiceResultKey = Object.keys(partData.dice[dice].results).find(
|
|
||||||
index => partData.dice[dice].results[index].active
|
|
||||||
);
|
|
||||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(
|
|
||||||
partData,
|
|
||||||
dice,
|
|
||||||
activeDiceResultKey
|
|
||||||
);
|
|
||||||
|
|
||||||
const rollData = this.party.system.tagTeam.members[memberKey].rollData;
|
|
||||||
rollData.options.damage[damageKey].parts = rollData.options.damage[damageKey].parts.map((damagePart, index) => {
|
|
||||||
if (index !== Number.parseInt(part)) return damagePart;
|
|
||||||
|
|
||||||
return {
|
|
||||||
...damagePart,
|
|
||||||
total: parsedRoll.total,
|
|
||||||
dice: rerolledDice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
rollData.options.damage[damageKey].total = rollData.options.damage[damageKey].parts.reduce((acc, part) => {
|
|
||||||
acc += part.total;
|
|
||||||
return acc;
|
|
||||||
}, 0);
|
|
||||||
|
|
||||||
|
const basePath = `system.tagTeam.members.${memberKey}.damageRollData`;
|
||||||
|
const updatePath = isResource ? `${basePath}.resources.${damageKey}` : `${basePath}.main`;
|
||||||
|
const updateValue = isResource ?
|
||||||
|
memberData.damageRollData.resources[damageKey] : memberData.damageRollData.main;
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members.${memberKey}.rollData`]: rollData
|
[updatePath]: updateValue.toJSON()
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
this.getUpdatingParts(button)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCriticalDamage(damage) {
|
async getCriticalDamage(origDamage) {
|
||||||
const newDamage = foundry.utils.deepClone(damage);
|
const newDamage = origDamage ? ChatDamageData.fromJSON(JSON.stringify(origDamage)) : null;
|
||||||
for (let key in newDamage) {
|
if (newDamage?.main) {
|
||||||
var damage = newDamage[key];
|
const criticalDamage = await getCritDamageBonus(newDamage.main.formula);
|
||||||
damage.formula = '';
|
|
||||||
damage.total = 0;
|
|
||||||
|
|
||||||
for (let part of damage.parts) {
|
|
||||||
const criticalDamage = await getCritDamageBonus(part.formula);
|
|
||||||
if (criticalDamage) {
|
if (criticalDamage) {
|
||||||
part.modifierTotal += criticalDamage;
|
const criticalTerm = new foundry.dice.terms.NumericTerm({ number: criticalDamage, evaluated: true });
|
||||||
part.total += criticalDamage;
|
criticalTerm.evaluate();
|
||||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
newDamage.main = await Roll.fromTerms([
|
||||||
part.roll = new Roll(part.formula);
|
...origDamage.main.terms,
|
||||||
}
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
|
criticalTerm
|
||||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
]);
|
||||||
damage.total += part.total;
|
newDamage.main.options = foundry.utils.deepClone(origDamage.main.options);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newDamage;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNonCriticalDamage(config) {
|
|
||||||
const newDamage = foundry.utils.deepClone(config.damage);
|
|
||||||
for (let key in newDamage) {
|
|
||||||
var damage = newDamage[key];
|
|
||||||
damage.formula = '';
|
|
||||||
damage.total = 0;
|
|
||||||
|
|
||||||
for (let part of damage.parts) {
|
|
||||||
const critDamageBonus = await getCritDamageBonus(part.formula);
|
|
||||||
part.modifierTotal -= critDamageBonus;
|
|
||||||
part.total -= critDamageBonus;
|
|
||||||
part.formula = `${part.dice.map(x => x.formula).join(' + ')} + ${part.modifierTotal}`;
|
|
||||||
part.roll = new Roll(part.formula);
|
|
||||||
|
|
||||||
damage.formula = [damage.formula, part.formula].filter(x => x).join(' + ');
|
|
||||||
damage.total += part.total;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -647,6 +600,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
static async #selectRoll(_, button) {
|
static async #selectRoll(_, button) {
|
||||||
const { memberKey } = button.dataset;
|
const { memberKey } = button.dataset;
|
||||||
|
|
||||||
this.updatePartyData(
|
this.updatePartyData(
|
||||||
{
|
{
|
||||||
[`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
|
[`system.tagTeam.members`]: Object.entries(this.party.system.tagTeam.members).reduce(
|
||||||
|
|
@ -657,7 +611,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
this.getUpdatingParts(button)
|
/* Selecting a roll must update all member sections hbs to display the correct damage information incase of a critical */
|
||||||
|
[
|
||||||
|
...Object.keys(this.party.system.tagTeam.members),
|
||||||
|
this.constructor.PARTS.rollSelection.id,
|
||||||
|
this.constructor.PARTS.result.id
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -675,29 +634,65 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||||
|
|
||||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
mainRoll.damageRollData = baseMainRoll.damageRollData ?
|
||||||
const systemData = mainRoll.rollData.options;
|
ChatDamageData.fromJSON(JSON.stringify(baseMainRoll.damageRollData)) : null;
|
||||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
const secondaryRoll = new MemberData(baseSecondaryRoll.toObject());
|
||||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
secondaryRoll.damageRollData = baseSecondaryRoll.damageRollData ?
|
||||||
|
ChatDamageData.fromJSON(JSON.stringify(baseSecondaryRoll.damageRollData)) : null;
|
||||||
|
|
||||||
if (secondaryRollData?.options.hasDamage) {
|
const isCritical = overrideIsCritical ?? mainRoll.roll.isCritical;
|
||||||
|
if (isCritical) mainRoll.damageRollData = await this.getCriticalDamage(mainRoll.damageRollData);
|
||||||
|
|
||||||
|
if (secondaryRoll.damageRollData) {
|
||||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
? await this.getCriticalDamage(secondaryRoll.damageRollData)
|
||||||
: secondaryRollData.options.damage;
|
: secondaryRoll.damageRollData;
|
||||||
if (systemData.damage) {
|
if (mainRoll.damageRollData) {
|
||||||
for (const [key, damage] of Object.entries(secondaryDamage ?? {})) {
|
if (secondaryDamage.main) {
|
||||||
if (key in systemData.damage) {
|
if (mainRoll.damageRollData.main) {
|
||||||
systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula]
|
mainRoll.damageRollData.main = Roll.fromTerms([
|
||||||
.filter(x => x)
|
...baseMainRoll.damageRollData.main.terms,
|
||||||
.join(' + ');
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
systemData.damage[key].total += damage.total;
|
...baseSecondaryRoll.damageRollData.main.terms
|
||||||
systemData.damage[key].parts.push(...damage.parts);
|
]);
|
||||||
|
|
||||||
|
/* Joining the roll.options of both rolls */
|
||||||
|
const joinedDamageTypes = new Set([
|
||||||
|
...baseMainRoll.damageRollData.main.options.damageTypes,
|
||||||
|
...baseSecondaryRoll.damageRollData.main.options.damageTypes
|
||||||
|
]);
|
||||||
|
mainRoll.damageRollData.main.options = {
|
||||||
|
...baseMainRoll.damageRollData.main.options,
|
||||||
|
damageTypes: [...joinedDamageTypes]
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
systemData.damage[key] = damage;
|
mainRoll.damageRollData.main = secondaryDamage.main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, damage] of Object.entries(secondaryDamage.resources ?? {})) {
|
||||||
|
if (key in mainRoll.damageRollData.resources) {
|
||||||
|
mainRoll.damageRollData.resources[key] = Roll.fromTerms([
|
||||||
|
...baseMainRoll.damageRollData.resources[key].terms,
|
||||||
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
|
...baseSecondaryRoll.damageRollData.resources[key].terms
|
||||||
|
]);
|
||||||
|
|
||||||
|
/* Joining the roll.options of both rolls */
|
||||||
|
const joinedDamageTypes = new Set([
|
||||||
|
...baseMainRoll.damageRollData.resources[key].options.damageTypes,
|
||||||
|
...baseSecondaryRoll.damageRollData.resources[key].options.damageTypes
|
||||||
|
]);
|
||||||
|
mainRoll.damageRollData.resources[key].options = {
|
||||||
|
...baseMainRoll.damageRollData.resources[key].options,
|
||||||
|
damageTypes: [...joinedDamageTypes]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
mainRoll.damageRollData.resources[key] = damage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
systemData.damage = secondaryDamage;
|
mainRoll.damageRollData = secondaryDamage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -752,13 +747,27 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
|
|
||||||
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor);
|
||||||
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||||
|
|
||||||
|
/* This could assumably be done better. For some reason rolls don't get correctly done through rollData.toJSON */
|
||||||
|
const systemData = {
|
||||||
|
...mainRoll.options,
|
||||||
|
damage: joinedRoll.damageRollData?.toJSON()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (joinedRoll.damageRollData.main) {
|
||||||
|
systemData.damage.main = joinedRoll.damageRollData.toJSON();
|
||||||
|
}
|
||||||
|
for (const type of Object.keys(joinedRoll.damageRollData?.resources ?? {})) {
|
||||||
|
systemData.damage.resources[type] = joinedRoll.damageRollData.resources[type].toJSON();
|
||||||
|
}
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage'),
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
msgData = {
|
msgData = {
|
||||||
type: 'dualityRoll',
|
type: 'dualityRoll',
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||||
speaker: cls.getSpeaker({ actor: mainActor }),
|
speaker: cls.getSpeaker({ actor: mainActor }),
|
||||||
system: mainRoll.options,
|
system: systemData,
|
||||||
rolls: [JSON.stringify(joinedRoll.roll)],
|
rolls: [JSON.stringify(joinedRoll.roll)],
|
||||||
sound: null,
|
sound: null,
|
||||||
flags: { core: { RollTable: true } }
|
flags: { core: { RollTable: true } }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { getUnusedDamageTypes } from '../../helpers/utils.mjs';
|
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
|
||||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -31,8 +31,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
removeElement: this.removeElement,
|
removeElement: this.removeElement,
|
||||||
removeTransformActor: this.removeTransformActor,
|
removeTransformActor: this.removeTransformActor,
|
||||||
editEffect: this.editEffect,
|
editEffect: this.editEffect,
|
||||||
addDamage: this.addDamage,
|
addDamage: this.#onAddDamage,
|
||||||
removeDamage: this.removeDamage,
|
removeDamage: this.#onRemoveDamage,
|
||||||
|
addDamageResource: this.#onAddDamageResource,
|
||||||
|
removeDamageResource: this.#onRemoveDamageResource,
|
||||||
editDoc: this.editDoc,
|
editDoc: this.editDoc,
|
||||||
addTrigger: this.addTrigger,
|
addTrigger: this.addTrigger,
|
||||||
removeTrigger: this.removeTrigger,
|
removeTrigger: this.removeTrigger,
|
||||||
|
|
@ -157,9 +159,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
context.config = CONFIG.DH;
|
context.config = CONFIG.DH;
|
||||||
if (this.action.damage) {
|
if (this.action.damage) {
|
||||||
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
|
||||||
|
context.allDamageTypesUsed = allKeys.every(k => k in this.action._source.damage.resources);
|
||||||
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
if (this.action.damage?.main?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +255,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
const submitData = this._prepareSubmitData(event, formData);
|
const submitData = this._prepareSubmitData(event, formData);
|
||||||
|
|
||||||
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
const data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||||
this.action = await this.action.update(data);
|
this.action = (await this.action.update(data)) ?? this.action;
|
||||||
|
|
||||||
this.sheetUpdate?.(this.action);
|
this.sheetUpdate?.(this.action);
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -299,53 +301,70 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static addDamage(_event) {
|
/** @this DHActionBaseConfig */
|
||||||
if (!this.action.damage.parts) return;
|
static #onAddDamage() {
|
||||||
|
if (!this.action.damage || this.action.damage?.main) return;
|
||||||
|
|
||||||
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
|
const data = this.action.toObject();
|
||||||
|
data.damage.main = {
|
||||||
|
...DHDamageData.schema.getInitialValue(),
|
||||||
|
applyTo: 'hitPoints',
|
||||||
|
type: 'physical'
|
||||||
|
};
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @this DHActionBaseConfig */
|
||||||
|
static #onRemoveDamage() {
|
||||||
|
if (!this.action.damage?.main) return;
|
||||||
|
const data = this.action.toObject();
|
||||||
|
data.damage.main = null;
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @this DHActionBaseConfig */
|
||||||
|
static #onAddDamageResource(_event) {
|
||||||
|
if (!this.action.damage) return;
|
||||||
|
|
||||||
|
const allKeys = Object.keys(CONFIG.DH.GENERAL.healingTypes);
|
||||||
|
const unused = allKeys.filter(k => !(k in this.action._source.damage.resources));
|
||||||
|
const choices = unused.map(k => ({ value: k, label: _loc(CONFIG.DH.GENERAL.healingTypes[k].label) }));
|
||||||
const content = new foundry.data.fields.StringField({
|
const content = new foundry.data.fields.StringField({
|
||||||
label: game.i18n.localize('Damage Type'),
|
label: _loc('DAGGERHEART.GENERAL.Resource.single'),
|
||||||
choices,
|
choices,
|
||||||
required: true
|
required: true
|
||||||
}).toFormGroup(
|
}).toFormGroup({}, {
|
||||||
{},
|
|
||||||
{
|
|
||||||
name: 'type',
|
name: 'type',
|
||||||
localize: true,
|
localize: true,
|
||||||
nameAttr: 'value',
|
nameAttr: 'value',
|
||||||
labelAttr: 'label'
|
labelAttr: 'label'
|
||||||
}
|
}).outerHTML;
|
||||||
).outerHTML;
|
|
||||||
|
|
||||||
const callback = (_, button) => {
|
const callback = (_, button) => {
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
const type = choices[button.form.elements.type.value].value;
|
const type = choices[button.form.elements.type.value].value;
|
||||||
const part = this.action.schema.fields.damage.fields.parts.element.getInitialValue();
|
data.damage.resources[type] = {
|
||||||
part.applyTo = type;
|
...this.action.schema.fields.damage.fields.resources.element.getInitialValue(),
|
||||||
if (type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
applyTo: type
|
||||||
part.type = this.action.schema.fields.damage.fields.parts.element.fields.type.element.initial;
|
};
|
||||||
|
|
||||||
data.damage.parts[type] = part;
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeDialog = new foundry.applications.api.DialogV2({
|
const typeDialog = new foundry.applications.api.DialogV2({
|
||||||
buttons: [
|
buttons: [
|
||||||
foundry.utils.mergeObject(
|
|
||||||
{
|
{
|
||||||
action: 'ok',
|
action: 'ok',
|
||||||
label: 'Confirm',
|
label: 'Confirm',
|
||||||
icon: 'fas fa-check',
|
icon: 'fas fa-check',
|
||||||
default: true
|
default: true,
|
||||||
},
|
callback
|
||||||
{ callback: callback }
|
}
|
||||||
)
|
|
||||||
],
|
],
|
||||||
content: content,
|
content: content,
|
||||||
rejectClose: false,
|
rejectClose: false,
|
||||||
modal: false,
|
modal: false,
|
||||||
window: {
|
window: {
|
||||||
title: game.i18n.localize('Add Damage')
|
title: _loc('DAGGERHEART.ACTIONS.Config.damage.addResource')
|
||||||
},
|
},
|
||||||
position: { width: 300 }
|
position: { width: 300 }
|
||||||
});
|
});
|
||||||
|
|
@ -353,12 +372,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
||||||
typeDialog.render(true);
|
typeDialog.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeDamage(_event, button) {
|
/** @this DHActionBaseConfig */
|
||||||
if (!this.action.damage.parts) return;
|
static #onRemoveDamageResource(_event, button) {
|
||||||
|
if (!this.action.damage?.resources) return;
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
const key = button.dataset.key;
|
const key = button.dataset.key;
|
||||||
delete data.damage.parts[key];
|
data.damage.resources[key] = _del;
|
||||||
data.damage.parts[`${key}`] = _del;
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,10 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
.querySelector('.stacking-change-checkbox')
|
.querySelector('.stacking-change-checkbox')
|
||||||
?.addEventListener('change', this.stackingChangeToggle.bind(this));
|
?.addEventListener('change', this.stackingChangeToggle.bind(this));
|
||||||
|
|
||||||
|
htmlElement
|
||||||
|
.querySelector('.range-dependence-change-checkbox')
|
||||||
|
?.addEventListener('change', this.rangeDependenceChangeToggle.bind(this));
|
||||||
|
|
||||||
htmlElement
|
htmlElement
|
||||||
.querySelector('.armor-change-checkbox')
|
.querySelector('.armor-change-checkbox')
|
||||||
?.addEventListener('change', this.armorChangeToggle.bind(this));
|
?.addEventListener('change', this.armorChangeToggle.bind(this));
|
||||||
|
|
@ -224,6 +228,20 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
return this.submit({ updateData: { system: systemData } });
|
return this.submit({ updateData: { system: systemData } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rangeDependenceChangeToggle(event) {
|
||||||
|
const rangeFields = this.document.system.schema.fields.rangeDependence.fields;
|
||||||
|
const systemData = {
|
||||||
|
rangeDependence: event.target.checked
|
||||||
|
? _replace({
|
||||||
|
type: rangeFields.type.initial,
|
||||||
|
target: rangeFields.target.initial,
|
||||||
|
range: rangeFields.range.initial
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
return this.submit({ updateData: { system: systemData } });
|
||||||
|
}
|
||||||
|
|
||||||
armorChangeToggle(event) {
|
armorChangeToggle(event) {
|
||||||
if (event.target.checked) {
|
if (event.target.checked) {
|
||||||
this.addArmorChange();
|
this.addArmorChange();
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { DHDamageData } from '../../data/fields/action/damageField.mjs';
|
||||||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
@ -8,8 +9,10 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
classes: ['adversary-settings'],
|
classes: ['adversary-settings'],
|
||||||
position: { width: 455, height: 'auto' },
|
position: { width: 455, height: 'auto' },
|
||||||
actions: {
|
actions: {
|
||||||
addExperience: DHAdversarySettings.#addExperience,
|
addExperience: this.#onAddExperience,
|
||||||
removeExperience: DHAdversarySettings.#removeExperience
|
removeExperience: this.#onRemoveExperience,
|
||||||
|
addDamage: this.#onAddDamage,
|
||||||
|
removeDamage: this.#onRemoveDamage
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -71,7 +74,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
* Adds a new experience entry to the actor.
|
* Adds a new experience entry to the actor.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #addExperience() {
|
static async #onAddExperience() {
|
||||||
const newExperience = {
|
const newExperience = {
|
||||||
name: 'Experience',
|
name: 'Experience',
|
||||||
modifier: 0
|
modifier: 0
|
||||||
|
|
@ -83,7 +86,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
* Removes an experience entry from the actor.
|
* Removes an experience entry from the actor.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #removeExperience(_, target) {
|
static async #onRemoveExperience(_, target) {
|
||||||
const experience = this.actor.system.experiences[target.dataset.experience];
|
const experience = this.actor.system.experiences[target.dataset.experience];
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
|
|
@ -98,4 +101,28 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
await this.actor.update({ [`system.experiences.${target.dataset.experience}`]: _del });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this DHAdversarySettings
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static #onAddDamage() {
|
||||||
|
this.actor.update({
|
||||||
|
'system.attack.damage.main': {
|
||||||
|
...DHDamageData.schema.getInitialValue(),
|
||||||
|
applyTo: 'hitPoints',
|
||||||
|
type: 'physical'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this DHAdversarySettings
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static #onRemoveDamage() {
|
||||||
|
this.actor.update({
|
||||||
|
'system.attack.damage.main': null
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
module/applications/sheets/actors/_types.d.ts
vendored
10
module/applications/sheets/actors/_types.d.ts
vendored
|
|
@ -1,8 +1,18 @@
|
||||||
import DhCompanion from '../../../data/actor/companion.mjs';
|
import DhCompanion from '../../../data/actor/companion.mjs';
|
||||||
|
import DhParty from '../../../data/actor/party.mjs';
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
|
||||||
declare module './companion.mjs' {
|
declare module './companion.mjs' {
|
||||||
export default interface DhCompanionSheet {
|
export default interface DhCompanionSheet {
|
||||||
actor: DhpActor<DhCompanion>;
|
actor: DhpActor<DhCompanion>;
|
||||||
|
document: DhpActor<DhCompanion>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module './party.mjs' {
|
||||||
|
export default interface PartySheet {
|
||||||
|
actor: DhpActor<DhParty>;
|
||||||
|
document: DhpActor<DhParty>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['adversary'],
|
classes: ['adversary'],
|
||||||
position: { width: 660, height: 766 },
|
position: { width: 645, height: 750 },
|
||||||
window: { resizable: true },
|
window: { resizable: true },
|
||||||
actions: {
|
actions: {
|
||||||
toggleHitPoints: AdversarySheet.#toggleHitPoints,
|
toggleHitPoints: AdversarySheet.#toggleHitPoints,
|
||||||
|
|
@ -58,12 +58,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs',
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/features.hbs',
|
||||||
scrollable: ['.feature-section']
|
scrollable: ['.feature-section']
|
||||||
},
|
},
|
||||||
notes: {
|
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs'
|
|
||||||
},
|
|
||||||
effects: {
|
effects: {
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs',
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/effects.hbs',
|
||||||
scrollable: ['.effects-sections']
|
scrollable: ['.effects-sections']
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets/actors/adversary/notes.hbs',
|
||||||
|
scrollable: ['.editor-content']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs';
|
||||||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs';
|
import { getArmorSources, getDocFromElement, getDocFromElementSync, itemAbleRollParse, sortBy } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
|
@ -29,6 +29,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||||
|
toggleItemReload: CharacterSheet.#onToggleItemReload,
|
||||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
|
||||||
useDowntime: this.useDowntime,
|
useDowntime: this.useDowntime,
|
||||||
|
|
@ -914,7 +915,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
const doc = await getDocFromElement(button);
|
const doc = await getDocFromElement(button);
|
||||||
const { available } = this.document.system.loadoutSlot;
|
const { available } = this.document.system.loadoutSlot;
|
||||||
if (doc.system.inVault && !available && !doc.system.loadoutIgnore) {
|
if (doc.system.inVault && !available && !doc.system.loadoutIgnore) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
return ui.notifications.warn('DAGGERHEART.UI.Notifications.loadoutMaxReached', { localize: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
await doc?.update({ 'system.inVault': !doc.system.inVault });
|
await doc?.update({ 'system.inVault': !doc.system.inVault });
|
||||||
|
|
@ -954,11 +955,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** */
|
|
||||||
static #advanceResourceDie(_, target) {
|
static #advanceResourceDie(_, target) {
|
||||||
this.updateResourceDie(target, true);
|
this.updateResourceDie(target, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #onToggleItemReload(_, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item || !item.system.resource?.max)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await item.update({
|
||||||
|
'system.resource.value': item.system.needsReload ?
|
||||||
|
itemAbleRollParse(item.system.resource.max, this.document, item) : 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
lowerResourceDie(event) {
|
lowerResourceDie(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
@ -1057,7 +1068,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
direction: 'DOWN'
|
direction: 'DOWN'
|
||||||
});
|
});
|
||||||
|
|
||||||
html.querySelectorAll('.armor-slot').forEach(element => {
|
html.querySelectorAll('.armor .slot').forEach(element => {
|
||||||
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
|
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1072,7 +1083,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
|
|
||||||
/** Update specific armor source */
|
/** Update specific armor source */
|
||||||
static async armorSourcePipUpdate(event) {
|
static async armorSourcePipUpdate(event) {
|
||||||
const target = event.target.closest('.armor-slot');
|
const target = event.target.closest('.slot');
|
||||||
const { uuid, value } = target.dataset;
|
const { uuid, value } = target.dataset;
|
||||||
const document = await foundry.utils.fromUuid(uuid);
|
const document = await foundry.utils.fromUuid(uuid);
|
||||||
|
|
||||||
|
|
@ -1100,7 +1111,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = target.closest('.slot-bar');
|
const container = target.closest('.slot-bar');
|
||||||
for (const armorSlot of container.querySelectorAll('.armor-slot i')) {
|
for (const armorSlot of container.querySelectorAll('.armor .slot i')) {
|
||||||
const index = Number.parseInt(armorSlot.dataset.index);
|
const index = Number.parseInt(armorSlot.dataset.index);
|
||||||
if (decreasing && index >= newCurrent) {
|
if (decreasing && index >= newCurrent) {
|
||||||
armorSlot.classList.remove('fa-shield');
|
armorSlot.classList.remove('fa-shield');
|
||||||
|
|
@ -1206,10 +1217,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.document.uuid === item.parent?.uuid) {
|
// Check for same actor drag/drop attempts
|
||||||
|
const isSameActor = this.document.uuid === item.parent?.uuid;
|
||||||
|
const loadoutFieldset = event.target.closest('[data-in-vault]');
|
||||||
|
const vaulting = loadoutFieldset?.dataset.inVault === 'true';
|
||||||
|
if (isSameActor && loadoutFieldset && item.type === 'domainCard' && vaulting !== item.system.inVault) {
|
||||||
|
// This is likely an attempt to vault or unvault an item
|
||||||
|
const { available } = this.document.system.loadoutSlot;
|
||||||
|
if (!vaulting && !available && !item.system.loadoutIgnore) {
|
||||||
|
return ui.notifications.warn('DAGGERHEART.UI.Notifications.loadoutMaxReached', { localize: true });
|
||||||
|
}
|
||||||
|
return item.update({ 'system.inVault': vaulting });
|
||||||
|
} else if (isSameActor) {
|
||||||
return super._onDropItem(event, item);
|
return super._onDropItem(event, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle beastforms
|
||||||
if (item.type === 'beastform') {
|
if (item.type === 'beastform') {
|
||||||
if (this.document.effects.find(x => x.type === 'beastform')) {
|
if (this.document.effects.find(x => x.type === 'beastform')) {
|
||||||
return ui.notifications.warn(
|
return ui.notifications.warn(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
|
export default class PartySheet extends DHBaseActorSheet {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
@ -24,17 +26,17 @@ export default class Party extends DHBaseActorSheet {
|
||||||
resizable: true
|
resizable: true
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
openDocument: Party.#openDocument,
|
openDocument: PartySheet.#onOpenDocument,
|
||||||
deletePartyMember: Party.#deletePartyMember,
|
deletePartyMember: PartySheet.#onDeletePartyMember,
|
||||||
toggleHope: Party.#toggleHope,
|
toggleHope: PartySheet.#onToggleHope,
|
||||||
toggleHitPoints: Party.#toggleHitPoints,
|
toggleHitPoints: PartySheet.#onToggleHitPoints,
|
||||||
toggleStress: Party.#toggleStress,
|
toggleStress: PartySheet.#onToggleStress,
|
||||||
toggleArmorSlot: Party.#toggleArmorSlot,
|
toggleArmorSlot: PartySheet.#onToggleArmorSlot,
|
||||||
tempBrowser: Party.#tempBrowser,
|
tempBrowser: PartySheet.#onTempBrowser,
|
||||||
refeshActions: Party.#refeshActions,
|
refreshActions: PartySheet.#onRefreshActions,
|
||||||
triggerRest: Party.#triggerRest,
|
triggerRest: PartySheet.#onTriggerRest,
|
||||||
tagTeamRoll: Party.#tagTeamRoll,
|
tagTeamRoll: PartySheet.#onTagTeamRoll,
|
||||||
groupRoll: Party.#groupRoll
|
groupRoll: PartySheet.#onGroupRoll
|
||||||
},
|
},
|
||||||
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
@ -72,6 +74,23 @@ export default class Party extends DHBaseActorSheet {
|
||||||
this._createSearchFilter();
|
this._createSearchFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_toggleDisabled(disabled) {
|
||||||
|
// Overriden to only disable text inputs by default if the user is a member
|
||||||
|
const isMember = this.actor.system.partyMembers.some(
|
||||||
|
m => m.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
|
||||||
|
);
|
||||||
|
if (!isMember) return super._toggleDisabled(disabled);
|
||||||
|
|
||||||
|
const form = this.form;
|
||||||
|
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||||
|
input.disabled = disabled;
|
||||||
|
}
|
||||||
|
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||||
|
element.classList.toggle('disabled', disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -199,7 +218,15 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #openDocument(_, target) {
|
/* -------------------------------------------- */
|
||||||
|
/* Event handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onOpenDocument(_, target) {
|
||||||
const uuid = target.dataset.uuid;
|
const uuid = target.dataset.uuid;
|
||||||
const document = await foundry.utils.fromUuid(uuid);
|
const document = await foundry.utils.fromUuid(uuid);
|
||||||
document?.sheet?.render(true);
|
document?.sheet?.render(true);
|
||||||
|
|
@ -207,9 +234,10 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a hope resource value.
|
* Toggles a hope resource value.
|
||||||
|
* @this PartySheet
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleHope(_, target) {
|
static async #onToggleHope(_, target) {
|
||||||
const hopeValue = Number.parseInt(target.dataset.value);
|
const hopeValue = Number.parseInt(target.dataset.value);
|
||||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
const newValue = actor.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
|
const newValue = actor.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
|
||||||
|
|
@ -219,9 +247,10 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a hp resource value.
|
* Toggles a hp resource value.
|
||||||
|
* @this PartySheet
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleHitPoints(_, target) {
|
static async #onToggleHitPoints(_, target) {
|
||||||
const hitPointsValue = Number.parseInt(target.dataset.value);
|
const hitPointsValue = Number.parseInt(target.dataset.value);
|
||||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
const newValue = actor.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
const newValue = actor.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||||
|
|
@ -231,9 +260,10 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a stress resource value.
|
* Toggles a stress resource value.
|
||||||
|
* @this PartySheet
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleStress(_, target) {
|
static async #onToggleStress(_, target) {
|
||||||
const stressValue = Number.parseInt(target.dataset.value);
|
const stressValue = Number.parseInt(target.dataset.value);
|
||||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
const newValue = actor.system.resources.stress.value >= stressValue ? stressValue - 1 : stressValue;
|
const newValue = actor.system.resources.stress.value >= stressValue ? stressValue - 1 : stressValue;
|
||||||
|
|
@ -243,9 +273,10 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggles a armor slot resource value.
|
* Toggles a armor slot resource value.
|
||||||
|
* @this PartySheet
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #toggleArmorSlot(_, target) {
|
static async #onToggleArmorSlot(_, target) {
|
||||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||||
const { value, max } = actor.system.armorScore;
|
const { value, max } = actor.system.armorScore;
|
||||||
const inputValue = Number.parseInt(target.dataset.value);
|
const inputValue = Number.parseInt(target.dataset.value);
|
||||||
|
|
@ -258,12 +289,19 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens Compedium Browser
|
* Opens Compedium Browser
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #tempBrowser(_, target) {
|
static async #onTempBrowser(_, target) {
|
||||||
new ItemBrowser().render({ force: true });
|
new ItemBrowser().render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #refeshActions() {
|
/**
|
||||||
|
* @todo Is this unused?
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onRefreshActions() {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
title: 'New Section',
|
title: 'New Section',
|
||||||
|
|
@ -281,7 +319,11 @@ export default class Party extends DHBaseActorSheet {
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #triggerRest(_, button) {
|
/**
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onTriggerRest(_, button) {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
||||||
|
|
@ -304,6 +346,30 @@ export default class Party extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onDeletePartyMember(event, target) {
|
||||||
|
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
|
||||||
|
if (!event.shiftKey) {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
|
||||||
|
name: doc.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
|
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||||
|
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||||
|
}
|
||||||
|
|
||||||
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
||||||
const actor = await foundry.utils.fromUuid(actorId);
|
const actor = await foundry.utils.fromUuid(actorId);
|
||||||
if (!actor || !actor?.isOwner) return;
|
if (!actor || !actor?.isOwner) return;
|
||||||
|
|
@ -312,11 +378,19 @@ export default class Party extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #tagTeamRoll() {
|
/**
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onTagTeamRoll() {
|
||||||
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
|
new game.system.api.applications.dialogs.TagTeamDialog(this.document).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(_params) {
|
/**
|
||||||
|
* @this PartySheet
|
||||||
|
* @type {ApplicationClickAction}
|
||||||
|
*/
|
||||||
|
static async #onGroupRoll(_params) {
|
||||||
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -460,11 +534,10 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @inheritdoc */
|
||||||
|
|
||||||
async _onDropActor(event, document) {
|
async _onDropActor(event, document) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
if (document instanceof DhpActor && PartySheet.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
||||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
if (currentMembers.includes(data.uuid)) {
|
if (currentMembers.includes(data.uuid)) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
||||||
|
|
@ -477,24 +550,4 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #deletePartyMember(event, target) {
|
|
||||||
const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
|
|
||||||
if (!event.shiftKey) {
|
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
|
||||||
window: {
|
|
||||||
title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
|
|
||||||
name: doc.name
|
|
||||||
})
|
|
||||||
},
|
|
||||||
content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
|
||||||
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
|
||||||
await this.document.update({ 'system.partyMembers': newMembersList });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -305,7 +305,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
_preSyncPartState(partId, newElement, priorElement, state) {
|
_preSyncPartState(partId, newElement, priorElement, state) {
|
||||||
super._preSyncPartState(partId, newElement, priorElement, state);
|
super._preSyncPartState(partId, newElement, priorElement, state);
|
||||||
for (const el of priorElement.querySelectorAll('.extensible.extended')) {
|
for (const el of priorElement.querySelectorAll('.extensible.extended')) {
|
||||||
const { actionId, itemUuid } = el.parentElement.dataset;
|
const { actionId, itemUuid } = el.closest('[data-item-uuid], [data-action-id]').dataset;
|
||||||
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
|
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
|
||||||
const newExtensible = newElement.querySelector(selector);
|
const newExtensible = newElement.querySelector(selector);
|
||||||
newExtensible?.classList.add('extended');
|
newExtensible?.classList.add('extended');
|
||||||
|
|
@ -603,7 +603,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
const doc = await fromUuid(itemUuid);
|
const doc = await fromUuid(itemUuid);
|
||||||
|
|
||||||
//get inventory-item description element
|
//get inventory-item description element
|
||||||
const descriptionElement = el.querySelector('.invetory-description');
|
const descriptionElement = el.querySelector('.inventory-description');
|
||||||
if (!doc || !descriptionElement) continue;
|
if (!doc || !descriptionElement) continue;
|
||||||
|
|
||||||
// localize the description (idk if it's still necessary)
|
// localize the description (idk if it's still necessary)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
addFeature: DHBaseItemSheet.#addFeature,
|
addFeature: DHBaseItemSheet.#addFeature,
|
||||||
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||||
addResource: DHBaseItemSheet.#addResource,
|
addResource: DHBaseItemSheet.#addResource,
|
||||||
removeResource: DHBaseItemSheet.#removeResource
|
removeResource: DHBaseItemSheet.#removeResource,
|
||||||
|
editGMNote: DHBaseItemSheet.#onEditGMNote
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: null, dropSelector: '.drop-section' },
|
{ dragSelector: null, dropSelector: '.drop-section' },
|
||||||
|
|
@ -76,10 +77,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
await super._preparePartContext(partId, context, options);
|
await super._preparePartContext(partId, context, options);
|
||||||
|
const TextEditor = foundry.applications.ux.TextEditor.implementation;
|
||||||
|
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'description':
|
case 'description':
|
||||||
context.enrichedDescription = await this.document.system.getEnrichedDescription();
|
context.enrichedDescription = await this.document.system.getEnrichedDescription({ gmNotes: false });
|
||||||
|
context.enrichedGMNotes = await TextEditor.implementation.enrichHTML(this.item.system.gmNotes, {
|
||||||
|
relativeTo: this.item,
|
||||||
|
rollData: this.item.getRollData(),
|
||||||
|
secrets: this.item.isOwner
|
||||||
|
})
|
||||||
break;
|
break;
|
||||||
case 'effects':
|
case 'effects':
|
||||||
await this._prepareEffectsContext(context, options);
|
await this._prepareEffectsContext(context, options);
|
||||||
|
|
@ -331,4 +338,45 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the Add GM Note button being pressed. This is only used when an item has no GM notes.
|
||||||
|
* Later edits to a GM note instead go through the normal editor toggle workflow.
|
||||||
|
* @this DHBaseItemSheet
|
||||||
|
*/
|
||||||
|
static #onEditGMNote() {
|
||||||
|
// Open the editor, which might be hidden. We remove the css class to hide temporarily
|
||||||
|
// so that menu auto resizing functions properly.
|
||||||
|
const editor = this.element.querySelector('prose-mirror[name="system.gmNotes"]');
|
||||||
|
const wasHidden = editor.classList.contains('hide-if-inactive');
|
||||||
|
editor.classList.remove('hide-if-inactive');
|
||||||
|
editor.open = true;
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (wasHidden) editor.classList.add('hide-if-inactive');
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
// Render an add gmnotes button if there are no set GM notes.
|
||||||
|
// We need to re-render on close since its possible to prosemirror to close *without* triggering a full re-render
|
||||||
|
if (game.user.isGM && !this.item.system.gmNotes) {
|
||||||
|
const description = this.element.querySelector('[name="system.description"]');
|
||||||
|
const addButton = () => {
|
||||||
|
if (description.querySelector('[data-action=editGMNote]')) return;
|
||||||
|
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.classList.add('icon', 'toggle', 'fa-regular', 'fa-note-medical');
|
||||||
|
button.dataset.action = 'editGMNote';
|
||||||
|
button.dataset.tooltip = 'DAGGERHEART.ITEMS.Base.addGMNote';
|
||||||
|
description.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
addButton();
|
||||||
|
description.addEventListener('close', () => addButton());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||||
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
|
||||||
|
import FearTracker from './fearTracker.mjs';
|
||||||
|
|
||||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -110,7 +111,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||||
},
|
},
|
||||||
callback: async li => {
|
onClick: async (_event, li) => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||||
message.update({ rolls: [reroll] });
|
message.update({ rolls: [reroll] });
|
||||||
|
|
@ -126,7 +127,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
: false;
|
: false;
|
||||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||||
},
|
},
|
||||||
callback: async li => {
|
onClick: async (_event, li) => {
|
||||||
const message = game.messages.get(li.dataset.messageId);
|
const message = game.messages.get(li.dataset.messageId);
|
||||||
const update = await message.system.getRerolledDamage();
|
const update = await message.system.getRerolledDamage();
|
||||||
message.update(update);
|
message.update(update);
|
||||||
|
|
@ -152,6 +153,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||||
);
|
);
|
||||||
|
for (const element of html.querySelectorAll('.roll-reload-check')) {
|
||||||
|
element.addEventListener('click', event => this.onRollReloadCheck(event, message));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setupHooks() {
|
setupHooks() {
|
||||||
|
|
@ -179,28 +183,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRollSimple(event, message) {
|
async onRollSimple(event, message) {
|
||||||
const buttonType = event.target.dataset.type ?? 'damage',
|
const buttonType = event.target.dataset.type ?? 'damage';
|
||||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
const total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0);
|
||||||
damages = {
|
const targets = Array.from(game.user.targets);
|
||||||
hitPoints: {
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
applyTo: 'hitPoints',
|
|
||||||
damageTypes: [],
|
|
||||||
total
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
targets = Array.from(game.user.targets);
|
|
||||||
|
|
||||||
if (targets.length === 0)
|
if (targets.length === 0)
|
||||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||||
|
for (const target of targets) {
|
||||||
targets.forEach(target => {
|
if (buttonType === 'healing') target.actor.takeHealing({ hitPoints: total });
|
||||||
if (buttonType === 'healing') target.actor.takeHealing(damages);
|
else target.actor.takeDamage({ total });
|
||||||
else target.actor.takeDamage(damages);
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async abilityUseButton(event, message) {
|
async abilityUseButton(event, message) {
|
||||||
|
|
@ -256,26 +247,17 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = game.messages.get(messageData._id);
|
const message = game.messages.get(messageData._id);
|
||||||
const target = event.target.closest('[data-die-index]');
|
const target = event.target.closest('[data-result]');
|
||||||
|
|
||||||
if (target.dataset.type === 'damage') {
|
if (target.dataset.type === 'damage') {
|
||||||
const { damageType, part, dice, result } = target.dataset;
|
const { isResource, damageType, dice, result } = target.dataset;
|
||||||
const damagePart = message.system.damage[damageType].parts[part];
|
await message.system.damage.rerollDamageDie(isResource, damageType, dice, result);
|
||||||
const { parsedRoll, rerolledDice } = await game.system.api.dice.DamageRoll.reroll(damagePart, dice, result);
|
|
||||||
const damageParts = message.system.damage[damageType].parts.map((damagePart, index) => {
|
const updatePath = isResource ? `system.damage.resources.${damageType}` : 'system.damage.main';
|
||||||
if (index !== Number(part)) return damagePart;
|
const updateValue = isResource ?
|
||||||
return {
|
message.system.damage.resources[damageType] : message.system.damage.main;
|
||||||
...damagePart,
|
await message.update({
|
||||||
total: parsedRoll.total,
|
[updatePath]: updateValue.toJSON()
|
||||||
dice: rerolledDice
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const updateMessage = game.messages.get(message._id);
|
|
||||||
await updateMessage.update({
|
|
||||||
[`system.damage.${damageType}`]: {
|
|
||||||
total: parsedRoll.total,
|
|
||||||
parts: damageParts
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
const rerollDice = message.system.roll.dice[target.dataset.dieIndex];
|
||||||
|
|
@ -297,4 +279,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
const actor = game.actors.get(event.target.dataset.actorId);
|
const actor = game.actors.get(event.target.dataset.actorId);
|
||||||
new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true });
|
new game.system.api.applications.dialogs.RiskItAllDialog(actor, resourceValue).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_toggleNotifications({ closing = false } = {}) {
|
||||||
|
super._toggleNotifications(closing)
|
||||||
|
FearTracker.handleOffSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
async onRollReloadCheck(_event, messageData) {
|
||||||
|
const message = game.messages.get(messageData._id);
|
||||||
|
const needsReload = await message.system.action.handleReload?.({ awaitRoll: true });
|
||||||
|
await message.update({ 'system.needsReload': needsReload });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,6 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
action: socketEvent.Refresh,
|
action: socketEvent.Refresh,
|
||||||
data: { refreshType: RefreshType.Countdown }
|
data: { refreshType: RefreshType.Countdown }
|
||||||
});
|
});
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static #addCountdown() {
|
static #addCountdown() {
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,11 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DhCountdowns extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
previousCountdownData = null;
|
previousCountdownData = null;
|
||||||
changedCountdownsForAnimation = new Set();
|
|
||||||
|
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.previousCountdownData =
|
this.previousCountdownData =
|
||||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns;
|
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns).countdowns;
|
||||||
this.setupHooks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -80,7 +77,15 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onFirstRender(context, options) {
|
||||||
|
await super._onFirstRender(context, options);
|
||||||
|
this._createContextMenu(this._getCountdownContextOptions, '.countdown-container[data-countdown]', {
|
||||||
|
parentClassHooks: false, fixed: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
|
@ -95,7 +100,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
/* Handle animations to draw attention to countdown values changing */
|
/* Handle animations to draw attention to countdown values changing */
|
||||||
const typesToAnimate = new Set();
|
const typesToAnimate = new Set();
|
||||||
for (const countdownKey of this.changedCountdownsForAnimation) {
|
for (const countdownKey of options.animate ?? []) {
|
||||||
const shimmerAnimation = [
|
const shimmerAnimation = [
|
||||||
{ backgroundPositionX: '98%' },
|
{ backgroundPositionX: '98%' },
|
||||||
{ backgroundPositionX: '0%' }
|
{ backgroundPositionX: '0%' }
|
||||||
|
|
@ -127,8 +132,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
const element = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`);
|
const element = this.element.querySelector(`.header-type-toggles .header-type[data-type="${type}"]`);
|
||||||
element?.animate(pulseAnimation, pulseTiming);
|
element?.animate(pulseAnimation, pulseTiming);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.changedCountdownsForAnimation.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns countdown data filtered by ownership */
|
/** Returns countdown data filtered by ownership */
|
||||||
|
|
@ -137,17 +140,15 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
||||||
key,
|
key,
|
||||||
countdown,
|
countdown,
|
||||||
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
|
ownership: countdown.getUserLevel(game.user)
|
||||||
}));
|
}));
|
||||||
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCountdownData() {
|
_getCountdownData() {
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
|
|
||||||
return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
return this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
||||||
const playersWithAccess = game.users.reduce((acc, user) => {
|
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||||
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
|
const ownership = countdown.getUserLevel(user);
|
||||||
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||||
acc.push(user);
|
acc.push(user);
|
||||||
}
|
}
|
||||||
|
|
@ -213,19 +214,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static #getPlayerOwnership(user, setting, countdown) {
|
|
||||||
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
|
||||||
|
|
||||||
const playerOwnership = countdown.ownership[user.id];
|
|
||||||
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
|
||||||
? setting.defaultOwnership
|
|
||||||
: playerOwnership;
|
|
||||||
}
|
|
||||||
|
|
||||||
cooldownRefresh = ({ refreshType }) => {
|
|
||||||
if (refreshType === RefreshType.Countdown) this.render();
|
|
||||||
};
|
|
||||||
|
|
||||||
static canPerformEdit() {
|
static canPerformEdit() {
|
||||||
if (game.user.isGM) return true;
|
if (game.user.isGM) return true;
|
||||||
|
|
||||||
|
|
@ -323,18 +311,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
action: socketEvent.Refresh,
|
action: socketEvent.Refresh,
|
||||||
data: { refreshType: RefreshType.Countdown }
|
data: { refreshType: RefreshType.Countdown }
|
||||||
});
|
});
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
|
||||||
}
|
|
||||||
|
|
||||||
setupHooks() {
|
|
||||||
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(options) {
|
async close(options) {
|
||||||
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||||
if (options.closeKey) return;
|
if (options.closeKey) return;
|
||||||
|
|
||||||
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
|
||||||
return super.close(options);
|
return super.close(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,4 +359,30 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
refreshType: RefreshType.Countdown
|
refreshType: RefreshType.Countdown
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
|
||||||
|
*/
|
||||||
|
_getCountdownContextOptions() {
|
||||||
|
/** @param {HTMLElement} element */
|
||||||
|
const getCountdownFromElement = element => {
|
||||||
|
const id = element.closest('[data-countdown]').dataset.countdown;
|
||||||
|
if (!id) return null;
|
||||||
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
return setting.countdowns[id ?? ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'CONTROLS.CommonDelete',
|
||||||
|
icon: 'fa-solid fa-trash',
|
||||||
|
visible: element => {
|
||||||
|
return getCountdownFromElement(element)?.isOwner;
|
||||||
|
},
|
||||||
|
onClick: (_, target) => {
|
||||||
|
getCountdownFromElement(target)?.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,12 @@ export default class DhEffectsDisplay extends HandlebarsApplicationMixin(Applica
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Debounce and slightly delayed request to re-render this panel. Necessary for situations where it is not possible
|
||||||
|
* to properly wait for promises to resolve before refreshing the UI.
|
||||||
|
*/
|
||||||
|
refresh = foundry.utils.debounce(this.render.bind(this), 50);
|
||||||
|
|
||||||
get element() {
|
get element() {
|
||||||
return document.body.querySelector('.daggerheart.dh-style.effects-display');
|
return document.body.querySelector('.daggerheart.dh-style.effects-display');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,14 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
this._dragData = {
|
||||||
|
isDragging: false,
|
||||||
|
startX: 0,
|
||||||
|
startY: 0,
|
||||||
|
startLeft: 0,
|
||||||
|
startTop: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -21,19 +29,20 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
classes: [],
|
classes: [],
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: false,
|
||||||
title: 'DAGGERHEART.GENERAL.fear',
|
title: 'DAGGERHEART.GENERAL.fear',
|
||||||
positioned: true,
|
positioned: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
minimizable: false
|
minimizable: false
|
||||||
},
|
},
|
||||||
|
classes: ['daggerheart', 'dh-style', 'fear-tracker'],
|
||||||
actions: {
|
actions: {
|
||||||
setFear: FearTracker.setFear,
|
setFear: FearTracker.setFear,
|
||||||
increaseFear: FearTracker.increaseFear
|
increaseFear: FearTracker.increaseFear
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
width: 222,
|
width: 540,
|
||||||
height: 222
|
height: 'auto'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -53,6 +62,10 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear;
|
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get fearPosition() {
|
||||||
|
return game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).fearPosition;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Rendering */
|
/* Rendering */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -63,15 +76,48 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
current = this.currentFear,
|
current = this.currentFear,
|
||||||
max = this.maxFear,
|
max = this.maxFear,
|
||||||
percent = (current / max) * 100,
|
percent = (current / max) * 100,
|
||||||
isGM = game.user.isGM;
|
isGM = game.user.isGM,
|
||||||
|
locked = false,
|
||||||
|
isFree = this.fearPosition == 'free';
|
||||||
|
|
||||||
return { display, current, max, percent, isGM };
|
return { display, current, max, percent, isGM, locked, isFree };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
async _preFirstRender(context, options) {
|
async _onRender(context, options) {
|
||||||
options.position =
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
this.#setupDragging();
|
||||||
|
this.#setupResizing();
|
||||||
|
|
||||||
|
const fearPosition = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).fearPosition;
|
||||||
|
|
||||||
|
if (options.isFirstRender) FearTracker.handleOffSet();
|
||||||
|
if (!options.force) return;
|
||||||
|
|
||||||
|
this.handleStyleElement(fearPosition);
|
||||||
|
|
||||||
|
switch (fearPosition) {
|
||||||
|
case 'topCenter':
|
||||||
|
document.getElementById('ui-top')?.appendChild(this.element);
|
||||||
|
break;
|
||||||
|
case 'bottomCenter':
|
||||||
|
document.getElementById('ui-bottom')?.prepend(this.element);
|
||||||
|
break;
|
||||||
|
case 'rightTop':
|
||||||
|
document.getElementById('ui-right-column-1')?.appendChild(this.element);
|
||||||
|
break;
|
||||||
|
case 'leftBottom':
|
||||||
|
document.getElementById('ui-left-column-1')?.insertBefore(this.element, document.getElementById('players'));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
document.body?.appendChild(this.element);
|
||||||
|
const position =
|
||||||
game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position;
|
game.user.getFlag(CONFIG.DH.id, 'app.resources.position') ?? FearTracker.DEFAULT_OPTIONS.position;
|
||||||
|
this.setPosition(position);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
|
|
@ -80,13 +126,29 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onPosition(position) {
|
handleStyleElement(fearPosition) {
|
||||||
game.user.setFlag(CONFIG.DH.id, 'app.resources.position', position);
|
for (const position of Object.values(CONFIG.DH.GENERAL.fearPosition)) {
|
||||||
|
this.element.classList.remove(position.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(options = {}) {
|
this.element.classList.add(fearPosition);
|
||||||
if (!options.allowed) return;
|
}
|
||||||
else super.close(options);
|
|
||||||
|
static handleOffSet() {
|
||||||
|
const fearTracker = document.getElementById('resources');
|
||||||
|
const hotbar = document.getElementById('hotbar');
|
||||||
|
|
||||||
|
if (!fearTracker) return;
|
||||||
|
|
||||||
|
const offset = Number(hotbar.style.getPropertyValue('--offset').replace(/px$/, '')) || 0;
|
||||||
|
|
||||||
|
if (offset > 0) return;
|
||||||
|
|
||||||
|
fearTracker.style.setProperty('--offset', `${offset - 13}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onPosition(position) {
|
||||||
|
game.user.setFlag(CONFIG.DH.id, 'app.resources.position', position);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async setFear(event, target) {
|
static async setFear(event, target) {
|
||||||
|
|
@ -110,4 +172,119 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
||||||
value
|
value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove methods later to use Foundry's dragger and resize methods
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Dragging handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
#setupDragging() {
|
||||||
|
const dragHandle = this.element.querySelector('.drag-handle');
|
||||||
|
if (!dragHandle) return;
|
||||||
|
dragHandle.addEventListener('mousedown', this.#onDragStart.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
#onDragStart(event) {
|
||||||
|
if (event.button !== 0) return;
|
||||||
|
this._dragData.isDragging = true;
|
||||||
|
this._dragData.startX = event.clientX;
|
||||||
|
this._dragData.startY = event.clientY;
|
||||||
|
const rect = this.element.getBoundingClientRect();
|
||||||
|
this._dragData.startLeft = rect.left;
|
||||||
|
this._dragData.startTop = rect.top;
|
||||||
|
this.element.style.cursor = 'grabbing';
|
||||||
|
|
||||||
|
this._dragHandler = this.#onDragging.bind(this);
|
||||||
|
this._dragEndHandler = this.#onDragEnd.bind(this);
|
||||||
|
window.addEventListener('mousemove', this._dragHandler);
|
||||||
|
window.addEventListener('mouseup', this._dragEndHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
#onDragging(event) {
|
||||||
|
if (!this._dragData.isDragging) return;
|
||||||
|
|
||||||
|
const dragX = event.clientX - this._dragData.startX;
|
||||||
|
const dragY = event.clientY - this._dragData.startY;
|
||||||
|
|
||||||
|
this.element.style.left = `${this._dragData.startLeft + dragX}px`;
|
||||||
|
this.element.style.top = `${this._dragData.startTop + dragY}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
#onDragEnd() {
|
||||||
|
if (!this._dragData.isDragging) return;
|
||||||
|
this._dragData.isDragging = false;
|
||||||
|
this.element.style.cursor = '';
|
||||||
|
|
||||||
|
if (this._dragHandler) window.removeEventListener('mousemove', this._dragHandler);
|
||||||
|
if (this._dragEndHandler) window.removeEventListener('mouseup', this._dragEndHandler);
|
||||||
|
|
||||||
|
const rect = this.element.getBoundingClientRect();
|
||||||
|
const pos = { top: rect.top, left: rect.left };
|
||||||
|
|
||||||
|
this.setPosition(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Resize handlers */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
#setupResizing() {
|
||||||
|
const resizeHandle = this.element.querySelector('.resize-handle');
|
||||||
|
if (!resizeHandle) return;
|
||||||
|
resizeHandle.addEventListener('mousedown', this.#onResizeStart.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
#onResizeStart(e) {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
let maxAllowedWidth = 10000;
|
||||||
|
|
||||||
|
this._resizeData = {
|
||||||
|
isResizing: true,
|
||||||
|
startX: e.clientX,
|
||||||
|
startY: e.clientY,
|
||||||
|
startWidth: this.element.offsetWidth,
|
||||||
|
startHeight: this.element.offsetHeight,
|
||||||
|
maxAllowedWidth: Math.max(50, maxAllowedWidth)
|
||||||
|
};
|
||||||
|
|
||||||
|
this._resizeHandler = this.#onResizing.bind(this);
|
||||||
|
this._resizeEndHandler = this.#onResizeEnd.bind(this);
|
||||||
|
window.addEventListener('mousemove', this._resizeHandler);
|
||||||
|
window.addEventListener('mouseup', this._resizeEndHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
#onResizing(e) {
|
||||||
|
if (!this._resizeData?.isResizing) return;
|
||||||
|
|
||||||
|
const currentDx = e.clientX - this._resizeData.startX;
|
||||||
|
const potentialWidth = Math.max(50, this._resizeData.startWidth + currentDx);
|
||||||
|
|
||||||
|
const width = Math.min(potentialWidth, this._resizeData.maxAllowedWidth);
|
||||||
|
|
||||||
|
this.element.style.width = `${width}px`;
|
||||||
|
|
||||||
|
if (width < 100) {
|
||||||
|
this.element.classList.add('narrow');
|
||||||
|
} else {
|
||||||
|
this.element.classList.remove('narrow');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#onResizeEnd() {
|
||||||
|
if (!this._resizeData?.isResizing) return;
|
||||||
|
this._resizeData.isResizing = false;
|
||||||
|
|
||||||
|
if (this._resizeHandler) window.removeEventListener('mousemove', this._resizeHandler);
|
||||||
|
if (this._resizeEndHandler) window.removeEventListener('mouseup', this._resizeEndHandler);
|
||||||
|
|
||||||
|
let width = parseFloat(this.element.style.width);
|
||||||
|
|
||||||
|
|
||||||
|
if (isNaN(width)) {
|
||||||
|
width = this.element.getBoundingClientRect().width;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setPosition({ width: width });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
return inBounds.length === 1 ? inBounds[0] : null;
|
return inBounds.length === 1 ? inBounds[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTemplateShape({ type, angle, range, direction } = {}) {
|
static getTemplateShape({ shapeType, angle, range, direction, hasHole } = {}) {
|
||||||
const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
|
const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
|
||||||
|
|
||||||
/* Length calculation */
|
/* Length calculation */
|
||||||
|
|
@ -112,11 +112,11 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
|
|
||||||
const shapeData = {
|
const shapeData = {
|
||||||
...canvas.mousePosition,
|
...canvas.mousePosition,
|
||||||
type: type,
|
type: shapeType,
|
||||||
direction: direction ?? 0
|
direction: direction ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (type) {
|
switch (shapeType) {
|
||||||
case rectangle.id:
|
case rectangle.id:
|
||||||
shapeData.width = length;
|
shapeData.width = length;
|
||||||
shapeData.height = length;
|
shapeData.height = length;
|
||||||
|
|
@ -145,7 +145,8 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
||||||
y: 0,
|
y: 0,
|
||||||
width: 1,
|
width: 1,
|
||||||
height: 1,
|
height: 1,
|
||||||
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1,
|
||||||
|
hole: hasHole
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,10 @@ export const advantageState = {
|
||||||
export const areaTypes = {
|
export const areaTypes = {
|
||||||
placed: {
|
placed: {
|
||||||
id: 'placed',
|
id: 'placed',
|
||||||
label: 'Placed Area'
|
label: 'DAGGERHEART.CONFIG.AreaTypes.placed.label'
|
||||||
|
},
|
||||||
|
attached: {
|
||||||
|
id: 'attached',
|
||||||
|
label: 'DAGGERHEART.CONFIG.AreaTypes.attached.label'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -90,19 +90,21 @@ export const BPModifiers = {
|
||||||
name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name',
|
name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name',
|
||||||
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.description',
|
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.description',
|
||||||
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.physical.dice',
|
key: 'system.bonuses.damage.physical.dice',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1d4'
|
value: '1d4'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.magical.dice',
|
key: 'system.bonuses.damage.magical.dice',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1d4'
|
value: '1d4'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -825,7 +825,7 @@ export const refreshTypes = {
|
||||||
export const itemAbilityCosts = {
|
export const itemAbilityCosts = {
|
||||||
resource: {
|
resource: {
|
||||||
id: 'resource',
|
id: 'resource',
|
||||||
label: 'DAGGERHEART.GENERAL.resource',
|
label: 'DAGGERHEART.GENERAL.Resource.single',
|
||||||
group: 'Global'
|
group: 'Global'
|
||||||
},
|
},
|
||||||
quantity: {
|
quantity: {
|
||||||
|
|
@ -924,6 +924,14 @@ export const fearDisplay = {
|
||||||
hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' }
|
hide: { value: 'hide', label: 'DAGGERHEART.SETTINGS.Appearance.fearDisplay.hide' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fearPosition = {
|
||||||
|
free: { value: 'free', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.free' },
|
||||||
|
topCenter: { value: 'topCenter', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.topCenter' },
|
||||||
|
bottomCenter: { value: 'bottomCenter', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.bottomCenter' },
|
||||||
|
rightTop: { value: 'rightTop', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.rightTop' },
|
||||||
|
leftBottom: { value: 'leftBottom', label: 'DAGGERHEART.SETTINGS.Appearance.fearPosition.leftBottom' }
|
||||||
|
};
|
||||||
|
|
||||||
export const basicOwnershiplevels = {
|
export const basicOwnershiplevels = {
|
||||||
0: { value: 0, label: 'OWNERSHIP.NONE' },
|
0: { value: 0, label: 'OWNERSHIP.NONE' },
|
||||||
2: { value: 2, label: 'OWNERSHIP.OBSERVER' },
|
2: { value: 2, label: 'OWNERSHIP.OBSERVER' },
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.channeling.effects.channeling.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.channeling.effects.channeling.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.channeling.effects.channeling.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.channeling.effects.channeling.description',
|
||||||
img: 'icons/magic/symbols/rune-sigil-horned-blue.webp',
|
img: 'icons/magic/symbols/rune-sigil-horned-blue.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.roll.spellcast',
|
key: 'system.bonuses.roll.spellcast',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1'
|
value: '1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
difficult: {
|
difficult: {
|
||||||
|
|
@ -55,44 +57,46 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.difficult.effects.difficult.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.difficult.effects.difficult.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.difficult.effects.difficult.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.difficult.effects.difficult.description',
|
||||||
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.traits.agility.value',
|
key: 'system.traits.agility.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.traits.strength.value',
|
key: 'system.traits.strength.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.traits.finesse.value',
|
key: 'system.traits.finesse.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.traits.instinct.value',
|
key: 'system.traits.instinct.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.traits.presence.value',
|
key: 'system.traits.presence.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.traits.knowledge.value',
|
key: 'system.traits.knowledge.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
flexible: {
|
flexible: {
|
||||||
|
|
@ -103,14 +107,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.flexible.effects.flexible.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.flexible.effects.flexible.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.flexible.effects.flexible.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.flexible.effects.flexible.description',
|
||||||
img: 'icons/magic/movement/abstract-ribbons-red-orange.webp',
|
img: 'icons/magic/movement/abstract-ribbons-red-orange.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1'
|
value: '1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
fortified: {
|
fortified: {
|
||||||
|
|
@ -121,14 +127,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.fortified.effects.fortified.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.fortified.effects.fortified.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.fortified.effects.fortified.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.fortified.effects.fortified.description',
|
||||||
img: 'icons/magic/defensive/shield-barrier-glowing-blue.webp',
|
img: 'icons/magic/defensive/shield-barrier-glowing-blue.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.rules.damageReduction.increasePerArmorMark',
|
key: 'system.rules.damageReduction.increasePerArmorMark',
|
||||||
mode: 5,
|
type: 'override',
|
||||||
value: '2'
|
value: '2'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
gilded: {
|
gilded: {
|
||||||
|
|
@ -139,14 +147,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.gilded.effects.gilded.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.gilded.effects.gilded.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.gilded.effects.gilded.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.gilded.effects.gilded.description',
|
||||||
img: 'icons/magic/control/control-influence-crown-gold.webp',
|
img: 'icons/magic/control/control-influence-crown-gold.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.traits.presence.value',
|
key: 'system.traits.presence.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1'
|
value: '1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
heavy: {
|
heavy: {
|
||||||
|
|
@ -157,14 +167,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.heavy.effects.heavy.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.heavy.effects.heavy.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.heavy.effects.heavy.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.heavy.effects.heavy.description',
|
||||||
img: 'icons/commodities/metal/ingot-worn-iron.webp',
|
img: 'icons/commodities/metal/ingot-worn-iron.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
hopeful: {
|
hopeful: {
|
||||||
|
|
@ -212,14 +224,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.magical.effects.magical.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.magical.effects.magical.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.magical.effects.magical.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.magical.effects.magical.description',
|
||||||
img: 'icons/magic/defensive/barrier-shield-dome-blue-purple.webp',
|
img: 'icons/magic/defensive/barrier-shield-dome-blue-purple.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.rules.damageReduction.magical',
|
key: 'system.rules.damageReduction.magical',
|
||||||
mode: 5,
|
type: 'override',
|
||||||
value: 1
|
value: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
painful: {
|
painful: {
|
||||||
|
|
@ -249,14 +263,16 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.physical.effects.physical.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.physical.effects.physical.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.physical.effects.physical.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.physical.effects.physical.description',
|
||||||
img: 'icons/commodities/stone/ore-pile-tan.webp',
|
img: 'icons/commodities/stone/ore-pile-tan.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.rules.damageReduction.physical',
|
key: 'system.rules.damageReduction.physical',
|
||||||
mode: 5,
|
type: 'override',
|
||||||
value: 1
|
value: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
quiet: {
|
quiet: {
|
||||||
|
|
@ -280,19 +296,21 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.reinforced.effects.reinforced.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.reinforced.effects.reinforced.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.reinforced.effects.reinforced.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.reinforced.effects.reinforced.description',
|
||||||
img: 'icons/magic/defensive/shield-barrier-glowing-triangle-green.webp',
|
img: 'icons/magic/defensive/shield-barrier-glowing-triangle-green.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bunuses.damageThresholds.major',
|
key: 'system.bunuses.damageThresholds.major',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '2'
|
value: '2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.bunuses.damageThresholds.severe',
|
key: 'system.bunuses.damageThresholds.severe',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '2'
|
value: '2'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
resilient: {
|
resilient: {
|
||||||
|
|
@ -326,19 +344,21 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.sharp.effects.sharp.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.sharp.effects.sharp.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.sharp.effects.sharp.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.sharp.effects.sharp.description',
|
||||||
img: 'icons/magic/defensive/shield-barrier-glowing-triangle-green.webp',
|
img: 'icons/magic/defensive/shield-barrier-glowing-triangle-green.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.dice',
|
key: 'system.bonuses.damage.primaryWeapon.dice',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1d4'
|
value: '1d4'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.secondaryWeapon.dice',
|
key: 'system.bonuses.damage.secondaryWeapon.dice',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1d4'
|
value: '1d4'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
shifting: {
|
shifting: {
|
||||||
|
|
@ -408,19 +428,21 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.veryHeavy.effects.veryHeavy.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.veryHeavy.effects.veryHeavy.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.veryHeavy.effects.veryHeavy.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.veryHeavy.effects.veryHeavy.description',
|
||||||
img: 'icons/commodities/metal/ingot-stamped-steel.webp',
|
img: 'icons/commodities/metal/ingot-stamped-steel.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-2'
|
value: '-2'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.traits.agility.value',
|
key: 'system.traits.agility.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
warded: {
|
warded: {
|
||||||
|
|
@ -431,15 +453,17 @@ export const armorFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.warded.effects.warded.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.warded.effects.warded.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.warded.effects.warded.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.warded.effects.warded.description',
|
||||||
img: 'icons/magic/defensive/barrier-shield-dome-pink.webp',
|
img: 'icons/magic/defensive/barrier-shield-dome-pink.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.resistance.magical.reduction',
|
key: 'system.resistance.magical.reduction',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '@system.armorScore',
|
value: '@system.armorScore',
|
||||||
priority: 21
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -488,10 +512,11 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
|
||||||
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
img: 'icons/skills/melee/shield-block-bash-blue.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -504,6 +529,7 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
bonded: {
|
bonded: {
|
||||||
|
|
@ -514,14 +540,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.bonded.effects.damage.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.bonded.effects.damage.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.bonded.effects.damage.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.bonded.effects.damage.description',
|
||||||
img: 'icons/magic/symbols/chevron-elipse-circle-blue.webp',
|
img: 'icons/magic/symbols/chevron-elipse-circle-blue.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '@system.levelData.level.current'
|
value: '@system.levelData.level.current'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
bouncing: {
|
bouncing: {
|
||||||
|
|
@ -553,19 +581,21 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.brave.effects.brave.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.brave.effects.brave.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.brave.effects.brave.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.brave.effects.brave.description',
|
||||||
img: 'icons/magic/life/heart-cross-strong-flame-purple-orange.webp',
|
img: 'icons/magic/life/heart-cross-strong-flame-purple-orange.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.damageThresholds.severe',
|
key: 'system.damageThresholds.severe',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: 'ITEM.@system.tier'
|
value: 'ITEM.@system.tier'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
brutal: {
|
brutal: {
|
||||||
|
|
@ -618,14 +648,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.description',
|
||||||
img: 'icons/magic/lightning/claws-unarmed-strike-teal.webp',
|
img: 'icons/magic/lightning/claws-unarmed-strike-teal.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.proficiency',
|
key: 'system.proficiency',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1'
|
value: '1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -660,14 +692,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.cumbersome.effects.cumbersome.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.cumbersome.effects.cumbersome.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.cumbersome.effects.cumbersome.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.cumbersome.effects.cumbersome.description',
|
||||||
img: 'icons/commodities/metal/mail-plate-steel.webp',
|
img: 'icons/commodities/metal/mail-plate-steel.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.traits.finesse.value',
|
key: 'system.traits.finesse.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
deadly: {
|
deadly: {
|
||||||
|
|
@ -707,15 +741,17 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.effects.deflecting.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.effects.deflecting.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.effects.deflecting.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.effects.deflecting.description',
|
||||||
img: 'icons/skills/melee/hand-grip-sword-strike-orange.webp',
|
img: 'icons/skills/melee/hand-grip-sword-strike-orange.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '@system.armorScore',
|
value: '@system.armorScore',
|
||||||
priority: 21
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -754,14 +790,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.effects.agility',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.effects.agility',
|
||||||
img: 'icons/skills/melee/strike-flail-spiked-pink.webp',
|
img: 'icons/skills/melee/strike-flail-spiked-pink.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.traits.agility.value',
|
key: 'system.traits.agility.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
devastating: {
|
devastating: {
|
||||||
|
|
@ -791,14 +829,14 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
|
||||||
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1'
|
value: '1'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
system: {
|
|
||||||
rangeDependence: {
|
rangeDependence: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
range: 'melee',
|
range: 'melee',
|
||||||
|
|
@ -902,14 +940,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.actions.greed.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.actions.greed.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.actions.greed.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.actions.greed.description',
|
||||||
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.proficiency',
|
key: 'system.proficiency',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '1'
|
value: '1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -951,14 +991,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.heavy.effects.heavy.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.heavy.effects.heavy.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.heavy.effects.heavy.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.heavy.effects.heavy.description',
|
||||||
img: 'icons/commodities/metal/ingot-worn-iron.webp',
|
img: 'icons/commodities/metal/ingot-worn-iron.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
hooked: {
|
hooked: {
|
||||||
|
|
@ -1066,14 +1108,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.massive.effects.massive.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.massive.effects.massive.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.massive.effects.massive.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.massive.effects.massive.description',
|
||||||
img: 'icons/skills/melee/strike-flail-destructive-yellow.webp',
|
img: 'icons/skills/melee/strike-flail-destructive-yellow.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '-1'
|
value: '-1'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
painful: {
|
painful: {
|
||||||
|
|
@ -1103,14 +1147,14 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.paired.effects.paired.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.paired.effects.paired.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.paired.effects.paired.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.paired.effects.paired.description',
|
||||||
img: 'icons/skills/melee/weapons-crossed-swords-yellow-teal.webp',
|
img: 'icons/skills/melee/weapons-crossed-swords-yellow-teal.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: 'ITEM.@system.tier + 1'
|
value: 'ITEM.@system.tier + 1'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
system: {
|
|
||||||
rangeDependence: {
|
rangeDependence: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
range: 'melee',
|
range: 'melee',
|
||||||
|
|
@ -1158,14 +1202,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.effects.persuasive.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.effects.persuasive.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.effects.persuasive.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.effects.persuasive.description',
|
||||||
img: 'icons/magic/control/hypnosis-mesmerism-eye.webp',
|
img: 'icons/magic/control/hypnosis-mesmerism-eye.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.traits.presence.value',
|
key: 'system.traits.presence.value',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '2'
|
value: '2'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -1203,6 +1249,7 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.protective.effects.protective.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.protective.effects.protective.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.protective.effects.protective.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.protective.effects.protective.description',
|
||||||
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
img: 'icons/skills/melee/shield-block-gray-orange.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'Armor',
|
key: 'Armor',
|
||||||
|
|
@ -1215,6 +1262,8 @@ export const weaponFeatures = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
quick: {
|
quick: {
|
||||||
|
|
@ -1244,14 +1293,16 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.reliable.effects.reliable.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.reliable.effects.reliable.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.reliable.effects.reliable.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.reliable.effects.reliable.description',
|
||||||
img: 'icons/skills/melee/strike-sword-slashing-red.webp',
|
img: 'icons/skills/melee/strike-sword-slashing-red.webp',
|
||||||
|
system: {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.roll.primaryWeapon.bonus',
|
key: 'system.bonuses.roll.primaryWeapon.bonus',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: 1
|
value: 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
reloading: {
|
reloading: {
|
||||||
|
|
@ -1341,7 +1392,7 @@ export const weaponFeatures = {
|
||||||
changes: [
|
changes: [
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
type: 'add',
|
||||||
value: '@system.traits.agility.value',
|
value: '@system.traits.agility.value',
|
||||||
priority: 21
|
priority: 21
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,10 @@ export const menu = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const gameSettings = {
|
export const gameSettings = {
|
||||||
|
/** @type {'Automation'} */
|
||||||
Automation: 'Automation',
|
Automation: 'Automation',
|
||||||
Metagaming: 'Metagaming',
|
Metagaming: 'Metagaming',
|
||||||
|
/** @type {'Homebrew'} */
|
||||||
Homebrew: 'Homebrew',
|
Homebrew: 'Homebrew',
|
||||||
appearance: 'Appearance',
|
appearance: 'Appearance',
|
||||||
GlobalOverrides: 'GlobalOverrides',
|
GlobalOverrides: 'GlobalOverrides',
|
||||||
|
|
@ -37,6 +39,7 @@ export const gameSettings = {
|
||||||
Fear: 'ResourcesFear'
|
Fear: 'ResourcesFear'
|
||||||
},
|
},
|
||||||
LevelTiers: 'LevelTiers',
|
LevelTiers: 'LevelTiers',
|
||||||
|
/** @type {'Countdowns'} */
|
||||||
Countdowns: 'Countdowns',
|
Countdowns: 'Countdowns',
|
||||||
LastMigrationVersion: 'LastMigrationVersion',
|
LastMigrationVersion: 'LastMigrationVersion',
|
||||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||||
|
|
@ -59,3 +62,18 @@ export const actionAutomationChoices = {
|
||||||
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.always'
|
label: 'DAGGERHEART.CONFIG.ActionAutomationChoices.always'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const reloadChoices = {
|
||||||
|
off: {
|
||||||
|
id: 'off',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ReloadChoices.off.label'
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
id: 'button',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ReloadChoices.button.label'
|
||||||
|
},
|
||||||
|
auto: {
|
||||||
|
id: 'auto',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ReloadChoices.auto.label'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -12,6 +12,7 @@ import * as HOOKS from './hooksConfig.mjs';
|
||||||
import * as TRIGGER from './triggerConfig.mjs';
|
import * as TRIGGER from './triggerConfig.mjs';
|
||||||
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
|
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
|
||||||
|
|
||||||
|
/** @type {"daggerheart"} */
|
||||||
export const SYSTEM_ID = 'daggerheart';
|
export const SYSTEM_ID = 'daggerheart';
|
||||||
|
|
||||||
export const SYSTEM = {
|
export const SYSTEM = {
|
||||||
|
|
|
||||||
7
module/data/_types.d.ts
vendored
Normal file
7
module/data/_types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { DhCountdown } from './countdowns.mjs'
|
||||||
|
|
||||||
|
declare module './countdowns.mjs' {
|
||||||
|
export default interface DhCountdowns {
|
||||||
|
countdowns: Record<string, DhCountdown>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,18 +13,19 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
if (this.damage.includeBase) {
|
if (this.damage.includeBase) {
|
||||||
const baseDamage = this.getParentHitPointDamage();
|
const baseDamage = this.getParentHitPointDamage();
|
||||||
if (baseDamage) {
|
if (baseDamage) {
|
||||||
if (!this.damage.parts.hitPoints) {
|
if (!this.damage.main) {
|
||||||
this.damage.parts.hitPoints = baseDamage;
|
this.damage.main = baseDamage;
|
||||||
} else {
|
} else {
|
||||||
for (const type of baseDamage.type) this.damage.parts.hitPoints.type.add(type);
|
for (const type of baseDamage.type) this.damage.main.type.add(type);
|
||||||
|
|
||||||
this.damage.parts.hitPoints.value.custom = {
|
this.damage.main.value.custom = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
formula: `${baseDamage.value.getFormula()} + ${this.damage.parts.hitPoints.value.getFormula()}`
|
formula: `${baseDamage.value.getFormula()} + ${this.damage.main.value.getFormula()}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.roll.useDefault) {
|
if (this.roll.useDefault) {
|
||||||
this.roll.trait = this.item.system.attack.roll.trait;
|
this.roll.trait = this.item.system.attack.roll.trait;
|
||||||
this.roll.type = 'attack';
|
this.roll.type = 'attack';
|
||||||
|
|
@ -33,24 +34,28 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
getParentHitPointDamage() {
|
getParentHitPointDamage() {
|
||||||
return this.item?.system?.attack.damage.parts.hitPoints;
|
return this.item?.system?.attack.damage.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
get damageFormula() {
|
get damageFormula() {
|
||||||
const hitPointsPart = this.damage.parts.hitPoints;
|
const hitPointsPart = this.damage.main;
|
||||||
if (!hitPointsPart) return '0';
|
if (!hitPointsPart) return '0';
|
||||||
|
|
||||||
return hitPointsPart.value.getFormula();
|
return hitPointsPart.value.getFormula();
|
||||||
}
|
}
|
||||||
|
|
||||||
get altDamageFormula() {
|
get altDamageFormula() {
|
||||||
const hitPointsPart = this.damage.parts.hitPoints;
|
const hitPointsPart = this.damage.main;
|
||||||
if (!hitPointsPart) return '0';
|
if (!hitPointsPart) return '0';
|
||||||
|
|
||||||
return hitPointsPart.valueAlt.getFormula();
|
return hitPointsPart.valueAlt.getFormula();
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
|
if (this.item?.system.needsReload) {
|
||||||
|
return ui.notifications.error(_loc('DAGGERHEART.UI.Notifications.reloadRequired', { weapon: this.item.name }));
|
||||||
|
}
|
||||||
|
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
|
|
||||||
if (result?.message?.system.action?.roll?.type === 'attack') {
|
if (result?.message?.system.action?.roll?.type === 'attack') {
|
||||||
|
|
@ -61,6 +66,23 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleReload(options = { awaitRoll: false }) {
|
||||||
|
const roll = await new Roll('1d6').evaluate();
|
||||||
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
|
if (options.awaitRoll)
|
||||||
|
await game.dice3d.showForRoll(roll, game.user, true);
|
||||||
|
else
|
||||||
|
game.dice3d.showForRoll(roll, game.user, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const needsToReload = roll.total === 1;
|
||||||
|
if (needsToReload) {
|
||||||
|
this.item.update({ 'system.resource.value': 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return needsToReload;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a localized label array for this item subtype.
|
* Generate a localized label array for this item subtype.
|
||||||
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
|
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
|
||||||
|
|
@ -73,7 +95,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||||
|
|
||||||
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
const useAltDamage = this.actor?.effects?.find(x => x.type === 'horde')?.active;
|
||||||
for (const { value, valueAlt, type } of damage.parts) {
|
for (const { value, valueAlt, type } of [damage.main, ...damage.resources].filter(d => !!d)) {
|
||||||
const usedValue = useAltDamage ? valueAlt : value;
|
const usedValue = useAltDamage ? valueAlt : value;
|
||||||
const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
const damageString = Roll.replaceFormulaData(usedValue.getFormula(), this.actor?.getRollData() ?? {});
|
||||||
const str = damageString
|
const str = damageString
|
||||||
|
|
@ -82,7 +104,7 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
x: game.i18n.localize('DAGGERHEART.GENERAL.damage')
|
||||||
});
|
});
|
||||||
|
|
||||||
const icons = Array.from(type)
|
const icons = Array.from(type ?? [])
|
||||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasDescription() {
|
||||||
|
return Boolean(this.description);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
||||||
*
|
*
|
||||||
|
|
@ -110,7 +114,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if the current user is the owner of the containing item */
|
/**
|
||||||
|
* Returns true if the current user is the owner of the containing item.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
get isOwner() {
|
get isOwner() {
|
||||||
return this.item?.isOwner ?? true;
|
return this.item?.isOwner ?? true;
|
||||||
}
|
}
|
||||||
|
|
@ -139,6 +146,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the first Actor parent found.
|
* Return the first Actor parent found.
|
||||||
|
* @returns {DhpActor | null}
|
||||||
*/
|
*/
|
||||||
get actor() {
|
get actor() {
|
||||||
return this.item instanceof DhpActor
|
return this.item instanceof DhpActor
|
||||||
|
|
@ -151,6 +159,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
/**
|
/**
|
||||||
* Returns true if the action is usable.
|
* Returns true if the action is usable.
|
||||||
* An action is usable on any actor type. For example, an adversary might have a base attack action.
|
* An action is usable on any actor type. For example, an adversary might have a base attack action.
|
||||||
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
get usable() {
|
get usable() {
|
||||||
const actor = this.actor;
|
const actor = this.actor;
|
||||||
|
|
@ -247,7 +256,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled) await this.toChat();
|
if (this.chatDisplay && !config.skips.createMessage && !config.actionChatMessageHandled)
|
||||||
|
await this.toChat(null, config);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
@ -279,7 +289,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
hasEffect: this.hasEffect,
|
hasEffect: this.hasEffect,
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
onSave: this.save?.damageMod,
|
onSave: this.save?.damageMod,
|
||||||
isDirect: !!this.damage?.direct,
|
|
||||||
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
selectedMessageMode: game.settings.get('core', 'messageMode'),
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll,
|
evaluate: this.hasRoll,
|
||||||
|
|
@ -297,20 +306,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.damage) {
|
if (this.damage) {
|
||||||
config.isDirect = this.damage.direct;
|
config.isDirect = !!this.damage.main?.direct;
|
||||||
|
|
||||||
const groupAttackTokens = this.damage.groupAttack
|
const groupAttackTokens = this.damage.main?.groupAttack
|
||||||
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
? game.system.api.fields.ActionFields.DamageField.getGroupAttackTokens(
|
||||||
this.actor.id,
|
this.actor.id,
|
||||||
this.damage.groupAttack
|
this.damage.main.groupAttack
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
config.damageOptions = {
|
config.damageOptions = {
|
||||||
groupAttack: this.damage.groupAttack
|
groupAttack: this.damage.main?.groupAttack
|
||||||
? {
|
? {
|
||||||
numAttackers: Math.max(groupAttackTokens.length, 1),
|
numAttackers: Math.max(groupAttackTokens.length, 1),
|
||||||
range: this.damage.groupAttack
|
range: this.damage.main.groupAttack
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
};
|
};
|
||||||
|
|
@ -420,11 +429,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasDamage() {
|
get hasDamage() {
|
||||||
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type !== 'healing';
|
return this.type !== 'healing' && (Boolean(this.damage.main) || !foundry.utils.isEmpty(this.damage.resources));
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasHealing() {
|
get hasHealing() {
|
||||||
return Boolean(Object.keys(this.damage?.parts ?? {}).length) && this.type === 'healing';
|
return this.type === 'healing' && !foundry.utils.isEmpty(this.damage.resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasSave() {
|
get hasSave() {
|
||||||
|
|
@ -447,11 +456,38 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
static migrateData(source) {
|
static migrateData(source) {
|
||||||
if (source.damage?.parts && Array.isArray(source.damage.parts)) {
|
if (source.damage?.parts && Array.isArray(source.damage.parts)) {
|
||||||
|
let hitPointsExists = source.damage.parts.some(x => x.applyTo === 'hitPoints');
|
||||||
source.damage.parts = source.damage.parts.reduce((acc, part) => {
|
source.damage.parts = source.damage.parts.reduce((acc, part) => {
|
||||||
|
if (!part.applyTo && hitPointsExists) return acc;
|
||||||
|
|
||||||
|
if (!part.applyTo) {
|
||||||
|
hitPointsExists = true;
|
||||||
|
part.applyTo = 'hitPoints';
|
||||||
|
}
|
||||||
|
|
||||||
acc[part.applyTo] = part;
|
acc[part.applyTo] = part;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source.damage?.parts && !source.damage.resources && !source.damage.main) {
|
||||||
|
source.damage.main = null;
|
||||||
|
source.damage.resources = {};
|
||||||
|
for (const [partKey, part] of Object.entries(source.damage.parts)) {
|
||||||
|
if (partKey === 'hitPoints' && source.type !== 'healing') {
|
||||||
|
source.damage.main = {
|
||||||
|
...part,
|
||||||
|
includeBase: source.damage.includeBase,
|
||||||
|
direct: source.damage.direct,
|
||||||
|
groupAttack: source.damage.groupAttack
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
source.damage.resources[partKey] = part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete source.damage.parts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,8 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
* @returns Formula string
|
* @returns Formula string
|
||||||
*/
|
*/
|
||||||
getDamageFormula() {
|
getDamageFormula() {
|
||||||
const strings = [];
|
if (!this.damage.main) return '';
|
||||||
for (const { value } of this.damage.parts) {
|
|
||||||
strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.join(' + ');
|
return Roll.replaceFormulaData(this.damage.main.value.getFormula(), this.actor?.getRollData() ?? {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,6 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' })
|
description: new fields.HTMLField({ label: 'DAGGERHEART.GENERAL.description' })
|
||||||
}),
|
}),
|
||||||
rangeDependence: new fields.SchemaField({
|
rangeDependence: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({
|
|
||||||
required: true,
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.GENERAL.enabled'
|
|
||||||
}),
|
|
||||||
type: new fields.StringField({
|
type: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
choices: CONFIG.DH.GENERAL.rangeInclusion,
|
choices: CONFIG.DH.GENERAL.rangeInclusion,
|
||||||
|
|
@ -80,7 +75,7 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
initial: CONFIG.DH.GENERAL.range.melee.id,
|
initial: CONFIG.DH.GENERAL.range.melee.id,
|
||||||
label: 'DAGGERHEART.GENERAL.range'
|
label: 'DAGGERHEART.GENERAL.range'
|
||||||
})
|
})
|
||||||
}),
|
}, { nullable: true, initial: null }),
|
||||||
stacking: new fields.SchemaField(
|
stacking: new fields.SchemaField(
|
||||||
{
|
{
|
||||||
value: new fields.NumberField({
|
value: new fields.NumberField({
|
||||||
|
|
@ -146,14 +141,8 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
description: '',
|
description: '',
|
||||||
transfer: options.transfer,
|
transfer: options.transfer,
|
||||||
statuses: [],
|
statuses: [],
|
||||||
changes: [],
|
|
||||||
system: {
|
system: {
|
||||||
rangeDependence: {
|
changes: []
|
||||||
enabled: false,
|
|
||||||
type: CONFIG.DH.GENERAL.rangeInclusion.withinRange.id,
|
|
||||||
target: CONFIG.DH.GENERAL.otherTargetTypes.hostile.id,
|
|
||||||
range: CONFIG.DH.GENERAL.range.melee.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -185,4 +174,12 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
||||||
if (this.parent.actor && options.scrollingTextData)
|
if (this.parent.actor && options.scrollingTextData)
|
||||||
this.parent.actor.queueScrollText(options.scrollingTextData);
|
this.parent.actor.queueScrollText(options.scrollingTextData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static migrateData(source) {
|
||||||
|
if (source.rangeDependence?.enabled === false) {
|
||||||
|
source.rangeDependence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.migrateData(source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,16 +84,15 @@ export default class DhpAdversary extends DhCreature {
|
||||||
type: 'attack'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: {
|
main: {
|
||||||
hitPoints: {
|
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
|
applyTo: 'hitPoints',
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'flat'
|
multiplier: 'flat'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
experiences: new fields.TypedObjectField(
|
experiences: new fields.TypedObjectField(
|
||||||
new fields.SchemaField({
|
new fields.SchemaField({
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,9 @@ export default class DhCharacter extends DhCreature {
|
||||||
trait: 'strength'
|
trait: 'strength'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: {
|
main: {
|
||||||
hitPoints: {
|
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
|
applyTo: 'hitPoints',
|
||||||
value: {
|
value: {
|
||||||
custom: {
|
custom: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
@ -116,7 +116,6 @@ export default class DhCharacter extends DhCreature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||||
bonuses: new fields.SchemaField({
|
bonuses: new fields.SchemaField({
|
||||||
|
|
@ -837,7 +836,7 @@ export default class DhCharacter extends DhCreature {
|
||||||
isReversed: true
|
isReversed: true
|
||||||
};
|
};
|
||||||
|
|
||||||
this.attack.damage.parts.hitPoints.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
this.attack.damage.main.value.custom.formula = `@prof${this.basicAttackDamageDice}${this.rules.attack.damage.bonus ? ` + ${this.rules.attack.damage.bonus}` : ''}`;
|
||||||
|
|
||||||
// Clamp resources (must be done last to ensure all updates occur)
|
// Clamp resources (must be done last to ensure all updates occur)
|
||||||
this.resources.clamp();
|
this.resources.clamp();
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ export default class DhCompanion extends DhCreature {
|
||||||
parts: {
|
parts: {
|
||||||
hitPoints: {
|
hitPoints: {
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
|
applyTo: 'hitPoints',
|
||||||
value: {
|
value: {
|
||||||
dice: 'd6',
|
dice: 'd6',
|
||||||
multiplier: 'prof'
|
multiplier: 'prof'
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,14 @@ import { calculateExpectedValue, parseTermsFromSimpleFormula } from '../../helpe
|
||||||
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
import { adversaryExpectedDamage, adversaryScalingData } from '../../config/actorConfig.mjs';
|
||||||
import { parseInlineParams } from '../../enrichers/parser.mjs';
|
import { parseInlineParams } from '../../enrichers/parser.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts source data for an adversary and a target tier, and returns new source data
|
||||||
|
* @type {object} source
|
||||||
|
* @type {number} tier
|
||||||
|
* @returns {object} adjusted source data
|
||||||
|
*/
|
||||||
export function getTierAdjustedAdversary(source, tier) {
|
export function getTierAdjustedAdversary(source, tier) {
|
||||||
const currentTier = source.tier ?? 1;
|
const currentTier = source.system.tier ?? 1;
|
||||||
|
|
||||||
/** @type {(2 | 3 | 4)[]} */
|
/** @type {(2 | 3 | 4)[]} */
|
||||||
const tiers = new Array(Math.abs(tier - currentTier))
|
const tiers = new Array(Math.abs(tier - currentTier))
|
||||||
|
|
@ -34,20 +40,20 @@ export function getTierAdjustedAdversary(source, tier) {
|
||||||
|
|
||||||
// Store initial attack damage for abilities that have you deal a "standard attack"
|
// Store initial attack damage for abilities that have you deal a "standard attack"
|
||||||
const initialAttack = {
|
const initialAttack = {
|
||||||
type: source.system.attack.damage?.parts.hitPoints?.type?.toSorted(),
|
type: source.system.attack.damage?.main?.type?.toSorted(),
|
||||||
value: getDamagePartsFormula(source.system.attack.damage?.parts.hitPoints?.value)
|
value: getFormula(source.system.attack.damage?.main?.value)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update damage of base attack.
|
// Update damage of base attack.
|
||||||
try {
|
try {
|
||||||
const damage = source.system.attack.damage;
|
const damage = source.system.attack.damage;
|
||||||
if (!damage?.parts.hitPoints) throw new Error('Unexpected missing attack in adversary');
|
if (!damage?.main) throw new Error('Unexpected missing damage in adversary');
|
||||||
|
|
||||||
for (const property of ['value', 'valueAlt']) {
|
for (const property of ['value', 'valueAlt']) {
|
||||||
const data = damage.parts.hitPoints[property];
|
const data = damage.main[property];
|
||||||
const previousFormula = getDamagePartsFormula(data);
|
const previousFormula = getFormula(data);
|
||||||
const { value, formula } = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
|
const value = calculateAdjustedDamage(previousFormula, 'attack', damageMeta);
|
||||||
applyAdjustedDamage(data, value, formula);
|
applyAdjustedDamage(data, value);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ui.notifications.warn('Failed to convert attack damage of adversary');
|
ui.notifications.warn('Failed to convert attack damage of adversary');
|
||||||
|
|
@ -65,7 +71,7 @@ export function getTierAdjustedAdversary(source, tier) {
|
||||||
if (!formula) return match;
|
if (!formula) return match;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newFormula = calculateAdjustedDamage(formula, 'action', damageMeta)?.formula;
|
const newFormula = getFormula(calculateAdjustedDamage(formula, 'action', damageMeta));
|
||||||
descriptionFormulas.push(formula);
|
descriptionFormulas.push(formula);
|
||||||
return match.replace(formula, newFormula);
|
return match.replace(formula, newFormula);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -76,21 +82,21 @@ export function getTierAdjustedAdversary(source, tier) {
|
||||||
|
|
||||||
// Update damage in item actions and convert all formula matches in the descriptions to the new damage
|
// 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)) {
|
for (const action of Object.values(item.system.actions)) {
|
||||||
if (!action.damage?.parts.hitPoints) continue;
|
if (!action.damage?.main) continue;
|
||||||
try {
|
try {
|
||||||
// Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
|
// Apply conversions and save a record. If it matches attack damage *and* Its not in the description, use attack conversion instead
|
||||||
const result = [];
|
const result = [];
|
||||||
for (const property of ['value', 'valueAlt']) {
|
for (const property of ['value', 'valueAlt']) {
|
||||||
const { [property]: data, type: damageType } = action.damage.parts.hitPoints;
|
const { [property]: data, type: damageType } = action.damage.main;
|
||||||
const previousFormula = getDamagePartsFormula(data);
|
const previousFormula = getFormula(data);
|
||||||
const isActuallyAttack =
|
const isActuallyAttack =
|
||||||
previousFormula === initialAttack.value &&
|
previousFormula === initialAttack.value &&
|
||||||
foundry.utils.equals(damageType.toSorted(), initialAttack.type) &&
|
foundry.utils.equals(damageType.toSorted(), initialAttack.type) &&
|
||||||
!descriptionFormulas.includes(previousFormula);
|
!descriptionFormulas.includes(previousFormula);
|
||||||
const type = isActuallyAttack ? 'attack' : 'action';
|
const type = isActuallyAttack ? 'attack' : 'action';
|
||||||
const { value, formula } = calculateAdjustedDamage(previousFormula, type, damageMeta);
|
const value = calculateAdjustedDamage(previousFormula, type, damageMeta);
|
||||||
applyAdjustedDamage(data, value, formula);
|
applyAdjustedDamage(data, value);
|
||||||
result.push({ previousFormula, formula });
|
result.push({ previousFormula, formula: getFormula(value) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override text in the description with those values
|
// Override text in the description with those values
|
||||||
|
|
@ -189,24 +195,30 @@ function calculateAdjustedDamage(formula, type, { currentDamageRange, newDamageR
|
||||||
value.bonus = Math.round(expected - getBaseAverage());
|
value.bonus = Math.round(expected - getBaseAverage());
|
||||||
}
|
}
|
||||||
|
|
||||||
const newFormula = [value.diceQuantity ? `${value.diceQuantity}d${value.faces}` : null, value.bonus]
|
return value;
|
||||||
.filter(p => !!p)
|
|
||||||
.join('+');
|
|
||||||
return { value, formula: newFormula };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDamagePartsFormula(data) {
|
/**
|
||||||
return data.custom.enabled
|
* Get formula from either damage data *or* a simple formula object.
|
||||||
? data.custom.formula
|
* @returns {string} the new formula data
|
||||||
: [data.flatMultiplier ? `${data.flatMultiplier}${data.dice}` : 0, data.bonus ?? 0].filter(p => !!p).join('+');
|
*/
|
||||||
|
function getFormula(data) {
|
||||||
|
if (data.custom?.enabled) {
|
||||||
|
return data.custom.formula;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diceQuantity = data.flatMultiplier ?? data.diceQuantity;
|
||||||
|
const dice = data.faces ? `d${data.faces}` : data.dice;
|
||||||
|
const mod = data.bonus;
|
||||||
|
return [diceQuantity ? `${diceQuantity}${dice}` : 0, mod].filter(p => !!p).join('+');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates damage to reflect a specific value.
|
* Updates damage to reflect a specific value.
|
||||||
* @throws if damage structure is invalid for conversion
|
* @param {object} diceData
|
||||||
* @returns the converted formula and value as a simplified term, or null if it doesn't deal HP damage
|
* @param {object} value
|
||||||
*/
|
*/
|
||||||
function applyAdjustedDamage(diceData, value, formula) {
|
function applyAdjustedDamage(diceData, value) {
|
||||||
if (value.diceQuantity) {
|
if (value.diceQuantity) {
|
||||||
diceData.custom.enabled = false;
|
diceData.custom.enabled = false;
|
||||||
diceData.bonus = value.bonus;
|
diceData.bonus = value.bonus;
|
||||||
|
|
@ -214,6 +226,6 @@ function applyAdjustedDamage(diceData, value, formula) {
|
||||||
diceData.flatMultiplier = value.diceQuantity;
|
diceData.flatMultiplier = value.diceQuantity;
|
||||||
} else if (!value.diceQuantity) {
|
} else if (!value.diceQuantity) {
|
||||||
diceData.custom.enabled = true;
|
diceData.custom.enabled = true;
|
||||||
diceData.custom.formula = formula;
|
diceData.custom.formula = getFormula(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||||
|
import { ChatDamageData } from './chatDamageData.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -41,6 +42,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
hasEffect: new fields.BooleanField({ initial: false }),
|
hasEffect: new fields.BooleanField({ initial: false }),
|
||||||
hasSave: new fields.BooleanField({ initial: false }),
|
hasSave: new fields.BooleanField({ initial: false }),
|
||||||
hasTarget: new fields.BooleanField({ initial: false }),
|
hasTarget: new fields.BooleanField({ initial: false }),
|
||||||
|
needsReload: new fields.BooleanField({ initial: false }),
|
||||||
isDirect: new fields.BooleanField({ initial: false }),
|
isDirect: new fields.BooleanField({ initial: false }),
|
||||||
onSave: new fields.StringField(),
|
onSave: new fields.StringField(),
|
||||||
source: new fields.SchemaField({
|
source: new fields.SchemaField({
|
||||||
|
|
@ -49,7 +51,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
originItem: originItemField(),
|
originItem: originItemField(),
|
||||||
action: new fields.StringField()
|
action: new fields.StringField()
|
||||||
}),
|
}),
|
||||||
damage: new fields.ObjectField(),
|
damage: new fields.EmbeddedDataField(ChatDamageData),
|
||||||
damageOptions: new fields.ObjectField(),
|
damageOptions: new fields.ObjectField(),
|
||||||
costs: new fields.ArrayField(new fields.ObjectField()),
|
costs: new fields.ArrayField(new fields.ObjectField()),
|
||||||
successConsumed: new fields.BooleanField({ initial: false })
|
successConsumed: new fields.BooleanField({ initial: false })
|
||||||
|
|
@ -132,28 +134,21 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Change how damage data is stored somehow to enable better rerolling */
|
|
||||||
async getRerolledDamage() {
|
async getRerolledDamage() {
|
||||||
if (!this.damage) return;
|
if (!this.damage.active) return;
|
||||||
|
|
||||||
const rerolls = [];
|
const rerolls = [];
|
||||||
const update = { system: { damage: {} } };
|
const update = { system: { damage: { main: null, resources: _replace({}) } } };
|
||||||
for (const partKey in this.damage) {
|
if (this.damage.main) {
|
||||||
const part = this.damage[partKey];
|
const reroll = await this.damage.main.reroll();
|
||||||
const testRoll = Roll.fromData(part.parts[0].roll);
|
rerolls.push(reroll);
|
||||||
const rerolled = await testRoll.reroll();
|
update.system.damage.main = reroll.toJSON();
|
||||||
rerolls.push(rerolled);
|
}
|
||||||
|
|
||||||
if (!update.system.damage[partKey]) update.system.damage[partKey] = { parts: [part.parts[0]] };
|
for (const key of Object.keys(this.damage.resources)) {
|
||||||
const partData = update.system.damage[partKey].parts[0];
|
const reroll = await this.damage.resources[key].reroll();
|
||||||
update.system.damage[partKey].total = rerolled.total;
|
rerolls.push(reroll);
|
||||||
partData.modifierTotal = rerolled.terms.reduce((acc, x) => {
|
update.system.damage.resources[key] = reroll.toJSON();
|
||||||
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);
|
await triggerChatRollFx(rerolls);
|
||||||
|
|
@ -198,6 +193,43 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
this.isGM = game.user.isGM; //temp
|
this.isGM = game.user.isGM; //temp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static migrateData(source) {
|
||||||
|
const { main, resources, ...flatDamageKeys } = source.damage ?? {};
|
||||||
|
if (source.damage && !main && !resources) {
|
||||||
|
source.damage.main = null;
|
||||||
|
source.damage.resources = {};
|
||||||
|
|
||||||
|
const getRoll = key => {
|
||||||
|
const damageData = source.damage[key];
|
||||||
|
const oldRoll = damageData.parts[0]?.roll;
|
||||||
|
return oldRoll ? JSON.stringify({
|
||||||
|
...oldRoll,
|
||||||
|
class: 'BaseRoll',
|
||||||
|
options: {
|
||||||
|
...oldRoll.options,
|
||||||
|
damageTypes: damageData.parts[0].damageTypes ?? []
|
||||||
|
}
|
||||||
|
}) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key of Object.keys(flatDamageKeys)) {
|
||||||
|
if (key === 'hitPoints' && source.hasDamage && !source.hasHealing) {
|
||||||
|
source.damage.main = getRoll('hitPoints');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
source.damage.resources[key] = getRoll(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(flatDamageKeys)) {
|
||||||
|
delete source.damage[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getTargetList() {
|
getTargetList() {
|
||||||
const targets =
|
const targets =
|
||||||
this.targetMode && this.parent.isAuthor
|
this.targetMode && this.parent.isAuthor
|
||||||
|
|
|
||||||
58
module/data/chat-message/chatDamageData.mjs
Normal file
58
module/data/chat-message/chatDamageData.mjs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
|
export class ChatDamageData extends foundry.abstract.DataModel {
|
||||||
|
constructor(data = {}, options = {}) {
|
||||||
|
super(data, options);
|
||||||
|
|
||||||
|
this._prepareRolls();
|
||||||
|
}
|
||||||
|
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
return {
|
||||||
|
main: new fields.JSONField({ nullable: true, validate: ChatDamageData.#validateRoll}),
|
||||||
|
resources: new fields.TypedObjectField(new fields.JSONField({validate: ChatDamageData.#validateRoll}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
get active() {
|
||||||
|
return !!this.main || Boolean(Object.keys(this.resources).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static #validateRoll(rollJSON) {
|
||||||
|
if (rollJSON) {
|
||||||
|
const roll = JSON.parse(rollJSON);
|
||||||
|
if (!roll.evaluated) throw new Error('Roll objects added to ChatMessage documents must be evaluated');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_prepareRolls() {
|
||||||
|
this.main &&= Roll.fromData(this.main);
|
||||||
|
for (const key of Object.keys(this.resources)) {
|
||||||
|
this.resources[key] = Roll.fromData(this.resources[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async rerollDamageDie(isResource, damageType, dice, resultIndex) {
|
||||||
|
const reroll = isResource ? this.resources[damageType] : this.main;
|
||||||
|
const rerollDice = reroll.dice[dice];
|
||||||
|
await rerollDice.rerollResult(resultIndex);
|
||||||
|
await reroll._evaluate();
|
||||||
|
|
||||||
|
const rerolledResult = rerollDice.results[rerollDice.results.length - 1];
|
||||||
|
if (rerolledResult) {
|
||||||
|
const fakeRoll = {
|
||||||
|
_evaluated: true,
|
||||||
|
dice: [new foundry.dice.terms.Die({
|
||||||
|
...rerollDice,
|
||||||
|
results: [rerolledResult],
|
||||||
|
total: rerolledResult.value,
|
||||||
|
faces: rerollDice.faces
|
||||||
|
})],
|
||||||
|
options: { appearance: {} }
|
||||||
|
};
|
||||||
|
await triggerChatRollFx([fakeRoll]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||||
import FormulaField from './fields/formulaField.mjs';
|
import FormulaField from './fields/formulaField.mjs';
|
||||||
|
|
||||||
export default class DhCountdowns extends foundry.abstract.DataModel {
|
export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||||
|
|
@ -14,19 +15,69 @@ export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange() {
|
/** @inheritdoc */
|
||||||
const previousCountdowns = foundry.ui.countdowns.previousCountdownData;
|
_initialize(options) {
|
||||||
const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => {
|
super._initialize(options);
|
||||||
const previousCountdown = previousCountdowns[key];
|
for (const [id, countdown] of Object.entries(this.countdowns)) {
|
||||||
if (!previousCountdown || (previousCountdown.progress.current !== countdown.progress.current)) {
|
countdown.id = id;
|
||||||
acc.push(key);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleChange() {
|
||||||
|
const previousCountdowns = foundry.ui.countdowns.previousCountdownData;
|
||||||
|
const changedCountdowns = Object.entries(this.countdowns).reduce((acc, [key, countdown]) => {
|
||||||
|
const previous = previousCountdowns[key];
|
||||||
|
const currentChanged = !previous || (previous.progress.current !== countdown.progress.current);
|
||||||
|
if (currentChanged && previous?.progress.start === countdown.progress.start) {
|
||||||
|
acc.push(key);
|
||||||
|
}
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
for (const countdownKey of changedCountdowns)
|
// Re-render countdowns applications. When the change is due to an actual update, resync the editor
|
||||||
foundry.ui.countdowns.changedCountdownsForAnimation.add(countdownKey);
|
if (!foundry.utils.equals(previousCountdowns, this.countdowns)) {
|
||||||
|
await foundry.ui.countdowns.render({ animate: changedCountdowns });
|
||||||
|
for (const instance of game.system.api.applications.ui.CountdownEdit.instances()) {
|
||||||
|
instance.data = this;
|
||||||
|
await instance.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform modules of updates
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
|
}
|
||||||
|
|
||||||
|
static migrateData(source) {
|
||||||
|
const migrateOldCountdowns = (data, type) => {
|
||||||
|
for (const key of Object.keys(data.countdowns)) {
|
||||||
|
const countdown = data.countdowns[key];
|
||||||
|
source.countdowns[key] = {
|
||||||
|
...countdown,
|
||||||
|
type: type,
|
||||||
|
ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => {
|
||||||
|
acc[key] =
|
||||||
|
countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type;
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
progress: {
|
||||||
|
...countdown.progress,
|
||||||
|
type: countdown.progress.type.value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
source[type] = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (source.narrative) {
|
||||||
|
migrateOldCountdowns(source.narrative, 'narrative');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.encounter) {
|
||||||
|
migrateOldCountdowns(source.encounter, 'encounter');
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.migrateData(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,6 +178,14 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean indicator for whether the current game User has ownership rights for this countdown
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get isOwner() {
|
||||||
|
return this.getUserLevel(game.user) === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static migrateData(source) {
|
static migrateData(source) {
|
||||||
if (source.progress.max) {
|
if (source.progress.max) {
|
||||||
|
|
@ -137,4 +196,29 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
return super.migrateData(source);
|
return super.migrateData(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the explicit permission level that a User has over this Document, a value in CONST.DOCUMENT_OWNERSHIP_LEVELS.
|
||||||
|
* Compendium content ignores the ownership field in favor of User role-based ownership. Otherwise, Documents use
|
||||||
|
* granular per-User ownership definitions and Embedded Documents defer to their parent ownership.
|
||||||
|
*
|
||||||
|
* @param {BaseUser} [user=game.user] The User being tested
|
||||||
|
* @returns {DocumentOwnershipNumber} A numeric permission level from {@link CONST.DOCUMENT_OWNERSHIP_LEVELS}
|
||||||
|
*/
|
||||||
|
getUserLevel(user) {
|
||||||
|
if (user.isGM) return CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||||
|
|
||||||
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
const playerOwnership = this.ownership[user.id];
|
||||||
|
return playerOwnership === undefined || playerOwnership === CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT
|
||||||
|
? setting.defaultOwnership
|
||||||
|
: playerOwnership;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete() {
|
||||||
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
const data = foundry.utils.deepClone(setting._source);
|
||||||
|
delete data.countdowns[this.id];
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ export default class AreasField extends fields.ArrayField {
|
||||||
initial: CONFIG.DH.GENERAL.range.veryClose.id,
|
initial: CONFIG.DH.GENERAL.range.veryClose.id,
|
||||||
label: 'DAGGERHEART.ACTIONS.Config.area.size'
|
label: 'DAGGERHEART.ACTIONS.Config.area.size'
|
||||||
}),
|
}),
|
||||||
|
hasHole: new fields.BooleanField({
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.area.hasHole'
|
||||||
|
}),
|
||||||
effects: new fields.ArrayField(new fields.DocumentIdField())
|
effects: new fields.ArrayField(new fields.DocumentIdField())
|
||||||
});
|
});
|
||||||
super(element, options, context);
|
super(element, options, context);
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,10 @@ export default class DamageField extends fields.SchemaField {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
constructor(options, context = {}) {
|
constructor(options, context = {}) {
|
||||||
const damageFields = {
|
super({
|
||||||
parts: new IterableTypedObjectField(DHDamageData),
|
main: new fields.EmbeddedDataField(DHDamageData, { nullable: true }),
|
||||||
includeBase: new fields.BooleanField({
|
resources: new IterableTypedObjectField(DHResourceData)
|
||||||
initial: false,
|
}, options, context);
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
|
||||||
}),
|
|
||||||
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
|
|
||||||
groupAttack: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.groupAttackRange,
|
|
||||||
blank: true,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
super(damageFields, options, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -41,25 +31,23 @@ export default class DamageField extends fields.SchemaField {
|
||||||
this.hasRoll &&
|
this.hasRoll &&
|
||||||
DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
|
DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id &&
|
||||||
!force
|
!force
|
||||||
)
|
) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let formulas = this.damage.parts.map(p => ({
|
const damageFormula = this.damage.main ?
|
||||||
formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
|
DamageField.formatFormulas.call(this, [this.damage.main], config)[0] : null;
|
||||||
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
const resourceFormulas = DamageField.formatFormulas.call(this, this.damage.resources, config);
|
||||||
applyTo: p.applyTo
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (!formulas.length) return false;
|
if (!damageFormula && !resourceFormulas.length) return false;
|
||||||
|
|
||||||
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
|
||||||
|
|
||||||
messageId = config.message?._id ?? messageId;
|
messageId = config.message?._id ?? messageId;
|
||||||
const message = game.messages.get(messageId);
|
const message = game.messages.get(messageId);
|
||||||
const damageConfig = {
|
const damageConfig = {
|
||||||
dialog: {},
|
dialog: {},
|
||||||
...config,
|
...config,
|
||||||
roll: formulas,
|
damageFormula,
|
||||||
|
resourceFormulas,
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
isCritical: Boolean(message?.system.roll?.isCritical)
|
isCritical: Boolean(message?.system.roll?.isCritical)
|
||||||
};
|
};
|
||||||
|
|
@ -92,7 +80,7 @@ export default class DamageField extends fields.SchemaField {
|
||||||
|
|
||||||
const targetDamage = [];
|
const targetDamage = [];
|
||||||
const damagePromises = [];
|
const damagePromises = [];
|
||||||
for (let target of targets) {
|
for (const target of targets) {
|
||||||
const actor = foundry.utils.fromUuidSync(target.actorId);
|
const actor = foundry.utils.fromUuidSync(target.actorId);
|
||||||
if (!actor) continue;
|
if (!actor) continue;
|
||||||
if (!config.hasHealing && config.onSave && target.saved?.success === true) {
|
if (!config.hasHealing && config.onSave && target.saved?.success === true) {
|
||||||
|
|
@ -114,13 +102,12 @@ export default class DamageField extends fields.SchemaField {
|
||||||
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
||||||
);
|
);
|
||||||
else {
|
else {
|
||||||
const configDamage = foundry.utils.deepClone(config.damage);
|
const configDamage = config.damage.clone();
|
||||||
const hpDamageMultiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
configDamage.main &&= configDamage.main.toJSON();
|
||||||
const hpDamageTakenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
if (configDamage.main) {
|
||||||
if (configDamage.hitPoints) {
|
const multiplier = config.actionActor?.system.rules?.attack?.damage?.hpDamageMultiplier ?? 1;
|
||||||
for (const part of configDamage.hitPoints.parts) {
|
const takenMultiplier = actor.system.rules?.attack?.damage?.hpDamageTakenMultiplier;
|
||||||
part.total = Math.ceil(part.total * hpDamageMultiplier * hpDamageTakenMultiplier);
|
configDamage.main.total = Math.ceil(configDamage.main.total * multiplier * takenMultiplier);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
damagePromises.push(
|
damagePromises.push(
|
||||||
|
|
@ -183,13 +170,19 @@ export default class DamageField extends fields.SchemaField {
|
||||||
/**
|
/**
|
||||||
* Prepare formulas for Damage Roll
|
* Prepare formulas for Damage Roll
|
||||||
* Must be called within Action context or similar.
|
* Must be called within Action context or similar.
|
||||||
* @param {object[]} formulas Array of formatted formulas object
|
* @param {DHResourceData[]} damageData Array of DHResourceData
|
||||||
* @param {object} data Action getRollData
|
* @param {object} data Action getRollData
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
static formatFormulas(formulas, data) {
|
static formatFormulas(damageData, data) {
|
||||||
|
const formulas = damageData.map(x => ({
|
||||||
|
formula: DamageField.getFormulaValue.call(this, x, data).getFormula(this.actor),
|
||||||
|
damageTypes: x.type ?? new Set(),
|
||||||
|
applyTo: x.applyTo
|
||||||
|
}));
|
||||||
|
|
||||||
const formattedFormulas = [];
|
const formattedFormulas = [];
|
||||||
formulas.forEach(formula => {
|
for (const formula of formulas) {
|
||||||
if (isNaN(formula.formula))
|
if (isNaN(formula.formula))
|
||||||
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
|
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
|
||||||
const same = formattedFormulas.find(
|
const same = formattedFormulas.find(
|
||||||
|
|
@ -197,7 +190,8 @@ export default class DamageField extends fields.SchemaField {
|
||||||
);
|
);
|
||||||
if (same) same.formula += ` + ${formula.formula}`;
|
if (same) same.formula += ` + ${formula.formula}`;
|
||||||
else formattedFormulas.push(formula);
|
else formattedFormulas.push(formula);
|
||||||
});
|
}
|
||||||
|
|
||||||
return formattedFormulas;
|
return formattedFormulas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,10 +269,18 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} the formula associated with this damage field
|
||||||
|
*/
|
||||||
getFormula() {
|
getFormula() {
|
||||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
if (this.custom.enabled) return this.custom.formula;
|
||||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
|
||||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`;
|
||||||
|
if (!multiplier) return String(this.bonus || 0);
|
||||||
|
|
||||||
|
const dice = `${multiplier ?? 1}${this.dice}`;
|
||||||
|
const sign = this.bonus < 0 ? ' - ' : ' + ';
|
||||||
|
return this.bonus ? `${dice} ${sign} ${Math.abs(this.bonus)}` : dice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,6 +288,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
|
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||||
applyTo: new fields.StringField({
|
applyTo: new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -308,7 +311,16 @@ export class DHDamageData extends DHResourceData {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
includeBase: new fields.BooleanField({
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||||
|
}),
|
||||||
|
direct: new fields.BooleanField({ initial: false, label: 'DAGGERHEART.CONFIG.DamageType.direct.name' }),
|
||||||
|
groupAttack: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.groupAttackRange,
|
||||||
|
blank: true,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.groupAttack.label'
|
||||||
|
}),
|
||||||
type: new fields.SetField(
|
type: new fields.SetField(
|
||||||
new fields.StringField({
|
new fields.StringField({
|
||||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs';
|
import { getWorldActor, itemAbleRollParse, triggerChatRollFx } from '../../../helpers/utils.mjs';
|
||||||
import FormulaField from '../formulaField.mjs';
|
import FormulaField from '../formulaField.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -23,7 +23,7 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
super(summonFields, options, context);
|
super(summonFields, options, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async execute() {
|
static async execute(config) {
|
||||||
if (!canvas.scene) {
|
if (!canvas.scene) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.summon.error'));
|
||||||
return;
|
return;
|
||||||
|
|
@ -36,13 +36,14 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
|
|
||||||
const rolls = [];
|
const rolls = [];
|
||||||
const summonData = [];
|
const summonData = [];
|
||||||
|
const chatMessageData = [];
|
||||||
for (const summon of this.summon) {
|
for (const summon of this.summon) {
|
||||||
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
||||||
await roll.evaluate();
|
await roll.evaluate();
|
||||||
const count = roll.total;
|
const count = roll.total;
|
||||||
if (!roll.isDeterministic) rolls.push(roll);
|
if (!roll.isDeterministic) rolls.push(roll);
|
||||||
|
|
||||||
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
const actor = await 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. */
|
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||||
summon.actor = actor.toObject();
|
summon.actor = actor.toObject();
|
||||||
|
|
||||||
|
|
@ -54,30 +55,18 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
|
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chatMessageData.push({
|
||||||
|
data: actor,
|
||||||
|
quantity: countNumber
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rolls.length) await triggerChatRollFx(rolls);
|
if (rolls.length) await triggerChatRollFx(rolls);
|
||||||
|
|
||||||
this.actor.sheet?.minimize();
|
this.actor.sheet?.minimize();
|
||||||
DHSummonField.handleSummon(summonData, this.actor);
|
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: this.actor.token?.elevation });
|
||||||
}
|
this.actor.sheet?.maximize();
|
||||||
|
config.summonData = chatMessageData;
|
||||||
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */
|
|
||||||
static async getWorldActor(baseActor) {
|
|
||||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
|
||||||
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
|
||||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
|
||||||
if (worldActorCopy) return worldActorCopy;
|
|
||||||
|
|
||||||
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
return baseActor;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async handleSummon(summonData, actionActor) {
|
|
||||||
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
|
|
||||||
|
|
||||||
return actionActor.sheet?.maximize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,11 @@
|
||||||
|
import { getWorldActor } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @import DHSummonAction from '../../action/summonAction.mjs'
|
||||||
|
*/
|
||||||
|
|
||||||
export default class DHSummonField extends fields.SchemaField {
|
export default class DHSummonField extends fields.SchemaField {
|
||||||
/**
|
/**
|
||||||
* Action Workflow order
|
* Action Workflow order
|
||||||
|
|
@ -20,6 +26,11 @@ export default class DHSummonField extends fields.SchemaField {
|
||||||
super(transformFields, options, context);
|
super(transformFields, options, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the execute. This is run on behalf of DHSummonAction.
|
||||||
|
* @todo move this function to be on the summon action.
|
||||||
|
* @this DHSummonAction
|
||||||
|
*/
|
||||||
static async execute() {
|
static async execute() {
|
||||||
if (!this.transform.actorUUID) {
|
if (!this.transform.actorUUID) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.noTransformActor'));
|
||||||
|
|
@ -37,26 +48,37 @@ export default class DHSummonField extends fields.SchemaField {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.actor.prototypeToken.actorLink) {
|
const activeTokens = this.actor.getActiveTokens(false, true);
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.actorLinkError'));
|
const controlledMatchingTokens = canvas.tokens.controlled
|
||||||
|
.filter(x => x.actor && x.actor.uuid === this.actor.uuid)
|
||||||
|
.map(x => x.document);
|
||||||
|
/** @type {typeof game.system.api.documents.DhToken | null} */
|
||||||
|
const token = this.actor.token ?? (
|
||||||
|
activeTokens.length === 1 ? activeTokens[0] :
|
||||||
|
(controlledMatchingTokens.length === 1 ? controlledMatchingTokens[0] : null)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this.actor.token && !token) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.linkedSelectedError'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.actor.token) {
|
if (!token) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const actor = await DHSummonField.getWorldActor(baseActor);
|
const actor = await getWorldActor(baseActor);
|
||||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
const tokenSize = actor?.system.metadata.usesSize ? tokenSizes[actor.system.size] : actor.prototypeToken.width;
|
const tokenSize = actor?.system.metadata.usesSize ? tokenSizes[actor.system.size] : actor.prototypeToken.width;
|
||||||
|
|
||||||
await this.actor.token.update(
|
// Update token. Avoid using recursive: false, since that prevents animations
|
||||||
{ ...actor.prototypeToken.toJSON(), actorId: actor.id, width: tokenSize, height: tokenSize },
|
await token.update(
|
||||||
{ diff: false, recursive: false, noHook: true }
|
{ ...actor.prototypeToken.toObject(), actorId: actor.id, width: tokenSize, height: tokenSize },
|
||||||
|
{ diff: false, noHook: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.actor.token.combatant) {
|
if (token.combatant) {
|
||||||
this.actor.token.combatant.update({ actorId: actor.id, img: actor.prototypeToken.texture.src });
|
this.actor.token.combatant.update({ actorId: actor.id, img: actor.prototypeToken.texture.src });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,17 +86,17 @@ export default class DHSummonField extends fields.SchemaField {
|
||||||
if (!this.transform.resourceRefresh.hitPoints) {
|
if (!this.transform.resourceRefresh.hitPoints) {
|
||||||
marks.hitPoints = Math.min(
|
marks.hitPoints = Math.min(
|
||||||
this.actor.system.resources.hitPoints.value,
|
this.actor.system.resources.hitPoints.value,
|
||||||
this.actor.token.actor.system.resources.hitPoints.max - 1
|
token.actor.system.resources.hitPoints.max - 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!this.transform.resourceRefresh.stress) {
|
if (!this.transform.resourceRefresh.stress) {
|
||||||
marks.stress = Math.min(
|
marks.stress = Math.min(
|
||||||
this.actor.system.resources.stress.value,
|
this.actor.system.resources.stress.value,
|
||||||
this.actor.token.actor.system.resources.stress.max - 1
|
token.actor.system.resources.stress.max - 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (marks.hitPoints || marks.stress) {
|
if (marks.hitPoints || marks.stress) {
|
||||||
this.actor.token.actor.update({
|
token.actor.update({
|
||||||
'system.resources': {
|
'system.resources': {
|
||||||
hitPoints: { value: marks.hitPoints },
|
hitPoints: { value: marks.hitPoints },
|
||||||
stress: { value: marks.stress }
|
stress: { value: marks.stress }
|
||||||
|
|
@ -84,20 +106,9 @@ export default class DHSummonField extends fields.SchemaField {
|
||||||
|
|
||||||
const prevPosition = { ...this.actor.sheet.position };
|
const prevPosition = { ...this.actor.sheet.position };
|
||||||
this.actor.sheet.close();
|
this.actor.sheet.close();
|
||||||
this.actor.token.actor.sheet.render({ force: true, position: prevPosition });
|
token.actor.sheet.render({ force: true, position: prevPosition });
|
||||||
}
|
if (token.object.controlled) {
|
||||||
|
ui.effectsDisplay.refresh();
|
||||||
/* Check for any available instances of the actor present in the world, or create a world actor based on compendium */
|
}
|
||||||
static async getWorldActor(baseActor) {
|
|
||||||
if (!baseActor.inCompendium) return baseActor;
|
|
||||||
|
|
||||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
|
||||||
if (dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
|
||||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
|
||||||
if (worldActorCopy) return worldActorCopy;
|
|
||||||
}
|
|
||||||
|
|
||||||
const worldActor = await game.system.api.documents.DhpActor.create(baseActor.toObject());
|
|
||||||
return worldActor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -238,6 +238,7 @@ export function ActionMixin(Base) {
|
||||||
result = this.parent;
|
result = this.parent;
|
||||||
} else {
|
} else {
|
||||||
result = await this.item.update({ [path]: updates }, options);
|
result = await this.item.update({ [path]: updates }, options);
|
||||||
|
if (!result) return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.inCollection
|
return this.inCollection
|
||||||
|
|
@ -269,10 +270,7 @@ export function ActionMixin(Base) {
|
||||||
return this.delete();
|
return this.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
async toChat(origin) {
|
async toChat(origin, config) {
|
||||||
const autoExpandDescription = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
|
||||||
.expandRollMessage?.desc;
|
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const systemData = {
|
const systemData = {
|
||||||
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||||
|
|
@ -282,7 +280,7 @@ export function ActionMixin(Base) {
|
||||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||||
areas: this.areas,
|
areas: this.areas,
|
||||||
summon: this.summon
|
summon: config?.summonData
|
||||||
},
|
},
|
||||||
source: {
|
source: {
|
||||||
actor: this.actor.uuid,
|
actor: this.actor.uuid,
|
||||||
|
|
@ -307,7 +305,7 @@ export function ActionMixin(Base) {
|
||||||
system: systemData,
|
system: systemData,
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/ui/chat/action.hbs',
|
'systems/daggerheart/templates/ui/chat/action.hbs',
|
||||||
{ ...systemData, open: autoExpandDescription ? 'open' : '' }
|
systemData
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
daggerheart: {
|
daggerheart: {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ export default class DHArmor extends AttachableItem {
|
||||||
type: 'armor',
|
type: 'armor',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isInventoryItem: true,
|
isInventoryItem: true,
|
||||||
hasActions: true
|
hasActions: true,
|
||||||
|
hasResource: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,6 +53,10 @@ export default class DHArmor extends AttachableItem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get itemFeatures() {
|
||||||
|
return this.armorFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async getDescriptionData() {
|
async getDescriptionData() {
|
||||||
const baseDescription = this.description;
|
const baseDescription = this.description;
|
||||||
|
|
@ -169,8 +174,4 @@ export default class DHArmor extends AttachableItem {
|
||||||
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
|
const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
|
||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
get itemFeatures() {
|
|
||||||
return this.armorFeatures;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.metadata.hasDescription) schema.description = new fields.HTMLField({ required: true, nullable: true });
|
if (this.metadata.hasDescription) {
|
||||||
|
schema.description = new fields.HTMLField({ required: true, nullable: true });
|
||||||
|
schema.gmNotes = new fields.HTMLField({ required: true, nullable: true });
|
||||||
|
}
|
||||||
|
|
||||||
if (this.metadata.hasResource) {
|
if (this.metadata.hasResource) {
|
||||||
schema.resource = new fields.SchemaField(
|
schema.resource = new fields.SchemaField(
|
||||||
|
|
@ -134,7 +137,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
/**
|
/**
|
||||||
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
* Augments the description for the item with type specific info to display. Implemented in applicable item subtypes.
|
||||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||||
* @returns {string}
|
* @returns {Promise<{ prefix: string | null; value: string | null; suffix: string | null }>}
|
||||||
*/
|
*/
|
||||||
async getDescriptionData(_options) {
|
async getDescriptionData(_options) {
|
||||||
return { prefix: null, value: this.description, suffix: null };
|
return { prefix: null, value: this.description, suffix: null };
|
||||||
|
|
@ -143,16 +146,26 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
/**
|
/**
|
||||||
* Gets the enriched and augmented description for the item.
|
* Gets the enriched and augmented description for the item.
|
||||||
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
* @param {object} [options] - Options that modify the styling of the rendered template. { headerStyle: undefined|'none'|'large' }
|
||||||
* @returns {string}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async getEnrichedDescription() {
|
async getEnrichedDescription({ gmNotes = true } = {}) {
|
||||||
if (!this.metadata.hasDescription) return '';
|
if (!this.metadata.hasDescription) return '';
|
||||||
|
|
||||||
const { prefix, value, suffix } = await this.getDescriptionData();
|
const { prefix, value, suffix } = await this.getDescriptionData();
|
||||||
const fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
let fullDescription = [prefix, value, suffix].filter(p => !!p).join('\n<hr>\n');
|
||||||
|
if (this.gmNotes && gmNotes) {
|
||||||
|
const gmNotesElement = document.createElement('section');
|
||||||
|
gmNotesElement.classList.add('gm-notes-section');
|
||||||
|
gmNotesElement.dataset.visibility = 'gm';
|
||||||
|
const header = document.createElement('header');
|
||||||
|
header.classList.add('gm-notes');
|
||||||
|
header.textContent = _loc('DAGGERHEART.ITEMS.FIELDS.gmNotes.label');
|
||||||
|
gmNotesElement.innerHTML = header.outerHTML + this.gmNotes;
|
||||||
|
fullDescription += gmNotesElement.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
return await foundry.applications.ux.TextEditor.implementation.enrichHTML(fullDescription, {
|
||||||
relativeTo: this,
|
relativeTo: this.parent,
|
||||||
rollData: this.getRollData(),
|
rollData: this.getRollData(),
|
||||||
secrets: this.parent.isOwner
|
secrets: this.parent.isOwner
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ export default class DHWeapon extends AttachableItem {
|
||||||
type: 'weapon',
|
type: 'weapon',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isInventoryItem: true,
|
isInventoryItem: true,
|
||||||
hasActions: true
|
hasActions: true,
|
||||||
|
hasResource: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,8 +67,7 @@ export default class DHWeapon extends AttachableItem {
|
||||||
type: 'attack'
|
type: 'attack'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: {
|
main: {
|
||||||
hitPoints: {
|
|
||||||
type: ['physical'],
|
type: ['physical'],
|
||||||
value: {
|
value: {
|
||||||
multiplier: 'prof',
|
multiplier: 'prof',
|
||||||
|
|
@ -76,7 +76,6 @@ export default class DHWeapon extends AttachableItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
attack: new fields.SchemaField({
|
attack: new fields.SchemaField({
|
||||||
|
|
@ -113,6 +112,18 @@ export default class DHWeapon extends AttachableItem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get itemFeatures() {
|
||||||
|
return this.weaponFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasReload() {
|
||||||
|
return Boolean(this.weaponFeatures.find(x => x.value === 'reloading'));
|
||||||
|
}
|
||||||
|
|
||||||
|
get needsReload() {
|
||||||
|
return this.hasReload && this.resource.value === 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async getDescriptionData() {
|
async getDescriptionData() {
|
||||||
const baseDescription = this.description;
|
const baseDescription = this.description;
|
||||||
|
|
@ -225,11 +236,12 @@ export default class DHWeapon extends AttachableItem {
|
||||||
game.i18n.localize(`DAGGERHEART.CONFIG.Burden.${burden}`)
|
game.i18n.localize(`DAGGERHEART.CONFIG.Burden.${burden}`)
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const { value, type } of attack.damage.parts) {
|
if (attack.damage.main) {
|
||||||
|
const { value, type } = attack.damage.main;
|
||||||
const parts = value.custom.enabled ? [game.i18n.localize('DAGGERHEART.GENERAL.custom')] : [value.dice];
|
const parts = value.custom.enabled ? [game.i18n.localize('DAGGERHEART.GENERAL.custom')] : [value.dice];
|
||||||
if (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString());
|
if (!value.custom.enabled && value.bonus) parts.push(value.bonus.signedString());
|
||||||
|
|
||||||
if (type.size > 0) {
|
if (type?.size) {
|
||||||
const typeTags = Array.from(type)
|
const typeTags = Array.from(type)
|
||||||
.map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`))
|
.map(t => game.i18n.localize(`DAGGERHEART.CONFIG.DamageType.${t}.abbreviation`))
|
||||||
.join(' | ');
|
.join(' | ');
|
||||||
|
|
@ -253,10 +265,10 @@ export default class DHWeapon extends AttachableItem {
|
||||||
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
|
if (roll.trait) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${roll.trait}.short`));
|
||||||
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
if (range) labels.push(game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.short`));
|
||||||
|
|
||||||
for (const { value, type } of damage.parts) {
|
for (const { value, type } of [damage.main, ...damage.resources].filter(d => !!d)) {
|
||||||
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
|
const str = Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {});
|
||||||
|
|
||||||
const icons = Array.from(type)
|
const icons = Array.from(type ?? [])
|
||||||
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
.map(t => CONFIG.DH.GENERAL.damageTypes[t]?.icon)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
|
@ -269,8 +281,4 @@ export default class DHWeapon extends AttachableItem {
|
||||||
|
|
||||||
return labels;
|
return labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
get itemFeatures() {
|
|
||||||
return this.weaponFeatures;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
||||||
choices: CONFIG.DH.GENERAL.fearDisplay,
|
choices: CONFIG.DH.GENERAL.fearDisplay,
|
||||||
initial: CONFIG.DH.GENERAL.fearDisplay.token.value
|
initial: CONFIG.DH.GENERAL.fearDisplay.token.value
|
||||||
}),
|
}),
|
||||||
|
fearPosition: new StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.fearPosition,
|
||||||
|
initial: CONFIG.DH.GENERAL.fearPosition.topCenter.value
|
||||||
|
}),
|
||||||
displayCountdownUI: new BooleanField({ initial: true }),
|
displayCountdownUI: new BooleanField({ initial: true }),
|
||||||
diceSoNice: new SchemaField({
|
diceSoNice: new SchemaField({
|
||||||
hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }),
|
hope: diceStyle({ fg: '#ffffff', bg: '#ffe760', outline: '#000000', edge: '#ffffff' }),
|
||||||
|
|
|
||||||
|
|
@ -196,6 +196,13 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
reload: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.SETTINGS.reloadChoices,
|
||||||
|
initial: CONFIG.DH.SETTINGS.reloadChoices.button.id,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.label',
|
||||||
|
hint: 'DAGGERHEART.SETTINGS.Automation.FIELDS.reload.hint'
|
||||||
|
}),
|
||||||
autoExpireActiveEffects: new fields.BooleanField({
|
autoExpireActiveEffects: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
initial: true,
|
initial: true,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ChatDamageData } from './chat-message/chatDamageData.mjs';
|
||||||
|
|
||||||
export default class TagTeamData extends foundry.abstract.DataModel {
|
export default class TagTeamData extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -37,6 +39,7 @@ export class MemberData extends foundry.abstract.DataModel {
|
||||||
}),
|
}),
|
||||||
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
rollChoice: new fields.StringField({ nullable: true, initial: null }),
|
||||||
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
rollData: new fields.JSONField({ nullable: true, initial: null }),
|
||||||
|
damageRollData: new fields.EmbeddedDataField(ChatDamageData),
|
||||||
selected: new fields.BooleanField({ initial: false })
|
selected: new fields.BooleanField({ initial: false })
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,8 @@ export default class BaseRoll extends Roll {
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs';
|
static TOOLTIP_TEMPLATE = 'systems/daggerheart/templates/ui/chat/foundryRollTooltip.hbs';
|
||||||
|
|
||||||
|
get modifierTotal() {
|
||||||
|
return this.total - this.dice.reduce((acc, dice) => acc + dice.total, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -185,8 +185,10 @@ export default class D20Roll extends DHRoll {
|
||||||
return changeKeys;
|
return changeKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
const data = super.postEvaluate(roll, config);
|
await super.buildEvaluate(roll, config, message);
|
||||||
|
|
||||||
|
const data = config.roll;
|
||||||
data.type = config.actionType;
|
data.type = config.actionType;
|
||||||
data.difficulty = config.roll.difficulty;
|
data.difficulty = config.roll.difficulty;
|
||||||
if (config.targets?.length) {
|
if (config.targets?.length) {
|
||||||
|
|
@ -222,7 +224,6 @@ export default class D20Roll extends DHRoll {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
data.modifierTotal = roll.modifierTotal;
|
data.modifierTotal = roll.modifierTotal;
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetFormula() {
|
resetFormula() {
|
||||||
|
|
|
||||||
|
|
@ -13,30 +13,34 @@ export default class DamageRoll extends DHRoll {
|
||||||
|
|
||||||
static DefaultDialog = DamageDialog;
|
static DefaultDialog = DamageDialog;
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static createRollInstance(config) {
|
||||||
if (config.dialog.configure === false) roll.constructFormula(config);
|
return new this(undefined, config.data, config);
|
||||||
if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate();
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static async buildEvaluate(roll, config = {}) {
|
||||||
|
if (config.dialog.configure === false) roll.constructFormulas(config);
|
||||||
|
|
||||||
|
const evaluateRoll = async roll => {
|
||||||
|
await roll.roll.evaluate();
|
||||||
|
roll.roll.options = { damageTypes: roll.damageTypes ? [...roll.damageTypes] : [] };
|
||||||
|
return roll.roll;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.damage) config.damage = { main: null, resources: {} };
|
||||||
|
|
||||||
|
if (config.damageFormula) {
|
||||||
|
config.damage.main = await evaluateRoll(config.damageFormula);
|
||||||
|
config.damage.main.options = { damageTypes:
|
||||||
|
config.damageFormula.damageTypes ? [...config.damageFormula.damageTypes] : []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const roll of config.resourceFormulas) {
|
||||||
|
config.damage.resources[roll.applyTo] = await evaluateRoll(roll);
|
||||||
|
}
|
||||||
|
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
|
||||||
return {
|
|
||||||
...roll,
|
|
||||||
...super.postEvaluate(roll.roll, config),
|
|
||||||
damageTypes: [...(roll.damageTypes ?? [])],
|
|
||||||
roll: roll.roll,
|
|
||||||
type: config.type,
|
|
||||||
modifierTotal: this.calculateTotalModifiers(roll.roll)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
|
|
@ -47,9 +51,10 @@ export default class DamageRoll extends DHRoll {
|
||||||
const diceRolls = [];
|
const diceRolls = [];
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
config.mute = true;
|
config.mute = true;
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
const pool = foundry.dice.terms.PoolTerm.fromRolls([
|
||||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
...(config.damage.main ? [config.damage.main] : []),
|
||||||
);
|
...Object.values(config.damage.resources)
|
||||||
|
]);
|
||||||
diceRolls.push(Roll.fromTerms([pool]));
|
diceRolls.push(Roll.fromTerms([pool]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,22 +65,14 @@ export default class DamageRoll extends DHRoll {
|
||||||
await super.buildPost(roll, config, message);
|
await super.buildPost(roll, config, message);
|
||||||
|
|
||||||
if (config.source?.message) {
|
if (config.source?.message) {
|
||||||
chatMessage.update({ 'system.damage': config.damage });
|
chatMessage.update({ 'system.damage': {
|
||||||
|
...config.damage.toObject(),
|
||||||
|
main: config.damage.main,
|
||||||
|
resources: config.damage.resources
|
||||||
|
}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static unifyDamageRoll(rolls) {
|
|
||||||
const unified = {};
|
|
||||||
rolls.forEach(r => {
|
|
||||||
const resource = unified[r.applyTo] ?? { formula: '', total: 0, parts: [] };
|
|
||||||
resource.formula += `${resource.formula !== '' ? ' + ' : ''}${r.formula}`;
|
|
||||||
resource.total += r.total;
|
|
||||||
resource.parts.push(r);
|
|
||||||
unified[r.applyTo] = resource;
|
|
||||||
});
|
|
||||||
return unified;
|
|
||||||
}
|
|
||||||
|
|
||||||
static formatGlobal(rolls) {
|
static formatGlobal(rolls) {
|
||||||
let formula, total;
|
let formula, total;
|
||||||
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
|
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
|
||||||
|
|
@ -124,11 +121,9 @@ export default class DamageRoll extends DHRoll {
|
||||||
const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
|
const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
|
||||||
const changeKeys = [];
|
const changeKeys = [];
|
||||||
|
|
||||||
for (const roll of this.options.roll) {
|
for (const damageType of this.options.damageFormula?.damageTypes?.values?.() ?? []) {
|
||||||
for (const damageType of roll.damageTypes?.values?.() ?? []) {
|
|
||||||
changeKeys.push(`system.bonuses.${type}.${damageType}`);
|
changeKeys.push(`system.bonuses.${type}.${damageType}`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const item = this.data.parent?.items?.get(this.options.source.item);
|
const item = this.data.parent?.items?.get(this.options.source.item);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|
@ -145,62 +140,69 @@ export default class DamageRoll extends DHRoll {
|
||||||
return changeKeys;
|
return changeKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormula(config) {
|
constructFormulas(config) {
|
||||||
|
return {
|
||||||
|
damageFormula: this.constructFormula(this.options.damageFormula, config, true),
|
||||||
|
resourceFormulas: this.options.resourceFormulas.map(x => this.constructFormula(x, config))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructFormula(formulaData, config, isDamage) {
|
||||||
|
if (!formulaData) return null;
|
||||||
this.options.isCritical = config.isCritical;
|
this.options.isCritical = config.isCritical;
|
||||||
for (const [index, part] of this.options.roll.entries()) {
|
|
||||||
const isHitpointPart = part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id;
|
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
part.modifiers = this.applyBaseBonus(part);
|
|
||||||
this.addModifiers(part);
|
|
||||||
part.modifiers?.forEach(m => {
|
|
||||||
part.roll.terms.push(...this.formatModifier(m.value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
formulaData.roll = new Roll(Roll.replaceFormulaData(formulaData.formula, config.data));
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
formulaData.roll.terms = Roll.parse(formulaData.roll.formula, config.data);
|
||||||
for (const mod in config.modifiers) {
|
|
||||||
const modifier = config.modifiers[mod];
|
|
||||||
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.extraFormula) {
|
if (formulaData.extraFormula) {
|
||||||
part.roll.terms.push(
|
formulaData.roll.terms.push(
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
...this.constructor.parse(part.extraFormula, this.options.data)
|
...this.constructor.parse(formulaData.extraFormula, this.options.data)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.damageOptions.groupAttack?.numAttackers > 1 && isHitpointPart) {
|
if (isDamage && formulaData.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
formulaData.modifiers = this.applyBaseBonus(formulaData);
|
||||||
|
this.addModifiers(formulaData);
|
||||||
|
formulaData.modifiers?.forEach(m => {
|
||||||
|
formulaData.roll.terms.push(...this.formatModifier(m.value));
|
||||||
|
});
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
for (const mod in config.modifiers) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (
|
||||||
|
modifier.beforeCrit === true &&
|
||||||
|
(modifier.enabled || modifier.value)
|
||||||
|
) modifier.callback(formulaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
for (const mod in config.modifiers) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(formulaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.damageOptions.groupAttack?.numAttackers > 1) {
|
||||||
const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
|
const damageTypes = [foundry.dice.terms.Die, foundry.dice.terms.NumericTerm];
|
||||||
for (const term of part.roll.terms) {
|
for (const term of formulaData.roll.terms) {
|
||||||
if (damageTypes.some(type => term instanceof type)) {
|
if (damageTypes.some(type => term instanceof type)) {
|
||||||
term.number *= config.damageOptions.groupAttack.numAttackers;
|
term.number *= config.damageOptions.groupAttack.numAttackers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.isCritical && isHitpointPart) {
|
if (config.isCritical) {
|
||||||
const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
const total = formulaData.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
|
||||||
if (total > 0) {
|
if (total > 0) {
|
||||||
part.roll.terms.push(...this.formatModifier(total));
|
formulaData.roll.terms.push(...this.formatModifier(total));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
formulaData.roll._formula = this.constructor.getFormula(formulaData.roll.terms);
|
||||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
|
||||||
for (const mod in config.modifiers) {
|
|
||||||
const modifier = config.modifiers[mod];
|
|
||||||
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
part.roll._formula = this.constructor.getFormula(part.roll.terms);
|
return formulaData;
|
||||||
}
|
|
||||||
return this.options.roll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To Remove When Reaction System */
|
/* To Remove When Reaction System */
|
||||||
|
|
@ -293,76 +295,4 @@ export default class DamageRoll extends DHRoll {
|
||||||
config.modifiers = mods;
|
config.modifiers = mods;
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async reroll(rollPart, dice, result) {
|
|
||||||
let diceIndex = 0;
|
|
||||||
let parsedRoll = game.system.api.dice.DamageRoll.fromData({
|
|
||||||
...rollPart.roll,
|
|
||||||
terms: rollPart.roll.terms.map(term => {
|
|
||||||
const isDie = term.class === 'Die';
|
|
||||||
const fixedTerm = {
|
|
||||||
...term,
|
|
||||||
...(isDie ? { results: rollPart.dice[diceIndex].results } : {})
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isDie) diceIndex++;
|
|
||||||
return fixedTerm;
|
|
||||||
}),
|
|
||||||
class: 'DamageRoll',
|
|
||||||
evaluated: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsedDiceTerms = Object.keys(parsedRoll.terms).reduce((acc, key) => {
|
|
||||||
const term = parsedRoll.terms[key];
|
|
||||||
if (term instanceof CONFIG.Dice.termTypes.DiceTerm) acc[Object.keys(acc).length] = term;
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
const term = parsedDiceTerms[dice];
|
|
||||||
const termResult = parsedDiceTerms[dice].results[result];
|
|
||||||
|
|
||||||
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];
|
|
||||||
diceRolls.push({
|
|
||||||
_evaluated: true,
|
|
||||||
dice: [
|
|
||||||
new foundry.dice.terms.Die({
|
|
||||||
...term,
|
|
||||||
total: newResult.result,
|
|
||||||
faces: term._faces,
|
|
||||||
results: [newResult]
|
|
||||||
})
|
|
||||||
],
|
|
||||||
options: { appearance: {} }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await triggerChatRollFx(diceRolls);
|
|
||||||
await parsedRoll.evaluate();
|
|
||||||
|
|
||||||
const results = parsedRoll.dice[dice].results.map(result => ({
|
|
||||||
...result,
|
|
||||||
discarded: !result.active
|
|
||||||
}));
|
|
||||||
const newResult = results.splice(results.length - 1, 1);
|
|
||||||
results.splice(Number(result) + 1, 0, newResult[0]);
|
|
||||||
|
|
||||||
const rerolledDice = parsedRoll.dice.map((x, index) => {
|
|
||||||
const isRerollDice = index === Number(dice);
|
|
||||||
if (!isRerollDice) return { ...x, dice: x.denomination };
|
|
||||||
return {
|
|
||||||
dice: parsedRoll.dice[dice].denomination,
|
|
||||||
total: parsedRoll.dice[dice].total,
|
|
||||||
results: results.map(result => ({
|
|
||||||
...result,
|
|
||||||
hasRerolls: result.hasRerolls || isRerollDice
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return { parsedRoll, rerolledDice };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||||
import { triggerChatRollFx } from '../helpers/utils.mjs';
|
import { triggerChatRollFx } from '../helpers/utils.mjs';
|
||||||
|
import BaseRoll from './baseRoll.mjs';
|
||||||
|
|
||||||
export default class DHRoll extends Roll {
|
export default class DHRoll extends BaseRoll {
|
||||||
baseTerms = [];
|
baseTerms = [];
|
||||||
constructor(formula, data = {}, options = {}) {
|
constructor(formula, data = {}, options = {}) {
|
||||||
super(formula, data, foundry.utils.mergeObject(options, { roll: [] }, { overwrite: false }));
|
super(formula, data, foundry.utils.mergeObject(options, { roll: [] }, { overwrite: false }));
|
||||||
|
|
@ -33,11 +34,17 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
if (config.skips?.createMessage) config.messageRoll = roll;
|
if (config.skips?.createMessage) config.messageRoll = roll;
|
||||||
|
|
||||||
|
if (config.evaluate !== false) {
|
||||||
await this.buildEvaluate(roll, config, (message = {}));
|
await this.buildEvaluate(roll, config, (message = {}));
|
||||||
|
}
|
||||||
await this.buildPost(roll, config, (message = {}));
|
await this.buildPost(roll, config, (message = {}));
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createRollInstance(config) {
|
||||||
|
return new this(config.roll.formula, config.data, config);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Partial<RollConfig>} config
|
* @param {Partial<RollConfig>} config
|
||||||
* @returns {Promise<RollConfig>}
|
* @returns {Promise<RollConfig>}
|
||||||
|
|
@ -55,7 +62,7 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
this.temporaryModifierBuilder(config);
|
this.temporaryModifierBuilder(config);
|
||||||
|
|
||||||
let roll = new this(config.roll.formula, config.data, config);
|
let roll = this.createRollInstance(config);
|
||||||
if (config.dialog.configure !== false) {
|
if (config.dialog.configure !== false) {
|
||||||
// Open Roll Dialog
|
// Open Roll Dialog
|
||||||
const DialogClass = config.dialog?.class ?? this.DefaultDialog;
|
const DialogClass = config.dialog?.class ?? this.DefaultDialog;
|
||||||
|
|
@ -72,27 +79,14 @@ export default class DHRoll extends Roll {
|
||||||
return roll;
|
return roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates the roll and assigns roll data into the config.
|
||||||
|
* This is only called if config.evaluate is not set to false
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
if (config.evaluate !== false) {
|
|
||||||
await roll.evaluate();
|
await roll.evaluate();
|
||||||
config.roll = this.postEvaluate(roll, config);
|
config.roll = {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async buildPost(roll, config, message) {
|
|
||||||
for (const hook of config.hooks) {
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postRoll${hook.capitalize()}`, config, message) === false) return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.skips?.createMessage) {
|
|
||||||
await triggerChatRollFx([roll]);
|
|
||||||
} else if (!config.source?.message) {
|
|
||||||
config.message = await this.toMessage(roll, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
|
||||||
return {
|
|
||||||
...roll.options.roll,
|
...roll.options.roll,
|
||||||
total: roll.total,
|
total: roll.total,
|
||||||
formula: roll.formula,
|
formula: roll.formula,
|
||||||
|
|
@ -105,9 +99,29 @@ export default class DHRoll extends Roll {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs any post configuration events that need to happen towards the end, such as hooks and dice so nice
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
static async buildPost(roll, config, message) {
|
||||||
|
for (const hook of config.hooks) {
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.postRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.skips?.createMessage) {
|
||||||
|
await triggerChatRollFx([roll]);
|
||||||
|
} else if (!config.source?.message) {
|
||||||
|
config.message = await this.toMessage(roll, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async toMessage(roll, config) {
|
static async toMessage(roll, config) {
|
||||||
const item = config.data.parent?.items?.get?.(config.source.item) ?? null;
|
const item = config.data.parent?.items?.get?.(config.source.item) ?? null;
|
||||||
const action = item ? item.system.actions.get(config.source.action) : null;
|
const actions = item ? [
|
||||||
|
...item.system.actions,
|
||||||
|
...(item.system.attack?.id === config.source.action ? [item.system.attack] : [])
|
||||||
|
] : [];
|
||||||
|
const action = actions.find(x => x.id === config.source.action);
|
||||||
let actionDescription = null;
|
let actionDescription = null;
|
||||||
if (action?.chatDisplay) {
|
if (action?.chatDisplay) {
|
||||||
actionDescription = action
|
actionDescription = action
|
||||||
|
|
@ -119,6 +133,14 @@ export default class DHRoll extends Roll {
|
||||||
config.actionChatMessageHandled = true;
|
config.actionChatMessageHandled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reloadSetting =
|
||||||
|
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).reload;
|
||||||
|
const useReload =
|
||||||
|
item?.system.hasReload &&
|
||||||
|
action?.type === 'attack' &&
|
||||||
|
reloadSetting === CONFIG.DH.SETTINGS.reloadChoices.auto.id;
|
||||||
|
const needsReload = useReload ? await action?.handleReload?.() : false;
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage'),
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
msgData = {
|
msgData = {
|
||||||
type: this.messageType,
|
type: this.messageType,
|
||||||
|
|
@ -126,7 +148,7 @@ export default class DHRoll extends Roll {
|
||||||
title: roll.title,
|
title: roll.title,
|
||||||
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
||||||
sound: config.mute ? null : CONFIG.sounds.dice,
|
sound: config.mute ? null : CONFIG.sounds.dice,
|
||||||
system: { ...config, actionDescription },
|
system: { ...config, actionDescription, needsReload },
|
||||||
rolls: [roll]
|
rolls: [roll]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -148,14 +170,17 @@ export default class DHRoll extends Roll {
|
||||||
if (!this._evaluated) return;
|
if (!this._evaluated) return;
|
||||||
|
|
||||||
const metagamingSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
const metagamingSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
||||||
|
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
||||||
return foundry.applications.handlebars.renderTemplate(template, {
|
return foundry.applications.handlebars.renderTemplate(template, {
|
||||||
roll: this,
|
roll: this,
|
||||||
...chatData,
|
...chatData,
|
||||||
|
action: chatData.action,
|
||||||
parent: chatData.parent,
|
parent: chatData.parent,
|
||||||
targetMode: chatData.targetMode,
|
targetMode: chatData.targetMode,
|
||||||
areas: chatData.action?.areas,
|
areas: chatData.action?.areas,
|
||||||
metagamingSettings
|
metagamingSettings,
|
||||||
|
automationSettings
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import HopeDie from './hopeDie.mjs';
|
||||||
import FearDie from './fearDie.mjs';
|
import FearDie from './fearDie.mjs';
|
||||||
import AdvantageDie from './advantageDie.mjs';
|
import AdvantageDie from './advantageDie.mjs';
|
||||||
import DisadvantageDie from './disadvantageDie.mjs';
|
import DisadvantageDie from './disadvantageDie.mjs';
|
||||||
|
export { default as BaseDie } from './baseDie.mjs';
|
||||||
|
|
||||||
export const diceTypes = {
|
export const diceTypes = {
|
||||||
DualityDie,
|
DualityDie,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export default class AdvantageDie extends foundry.dice.terms.Die {
|
import BaseDie from './baseDie.mjs';
|
||||||
|
|
||||||
|
export default class AdvantageDie extends BaseDie {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
|
||||||
12
module/dice/die/baseDie.mjs
Normal file
12
module/dice/die/baseDie.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export default class BaseDie extends foundry.dice.terms.Die {
|
||||||
|
async rerollResult(resultIndex) {
|
||||||
|
const result = this.results[resultIndex];
|
||||||
|
result.rerolled = true;
|
||||||
|
result.active = false;
|
||||||
|
await this.roll({ reroll: true });
|
||||||
|
|
||||||
|
const rerolledResult = this.results[this.results.length - 1];
|
||||||
|
this.results.splice(this.results.length - 1, 1);
|
||||||
|
this.results.splice(resultIndex, 0, rerolledResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
export default class DisadvantageDie extends foundry.dice.terms.Die {
|
import BaseDie from './baseDie.mjs';
|
||||||
|
|
||||||
|
export default class DisadvantageDie extends BaseDie {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
|
import BaseDie from './baseDie.mjs';
|
||||||
import { updateResourcesForDualityReroll } from '../helpers.mjs';
|
import { updateResourcesForDualityReroll } from '../helpers.mjs';
|
||||||
|
|
||||||
export default class DualityDie extends foundry.dice.terms.Die {
|
export default class DualityDie extends BaseDie {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,22 +232,10 @@ export default class DualityRoll extends D20Roll {
|
||||||
return changeKeys;
|
return changeKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
await super.buildEvaluate(roll, config, message);
|
await super.buildEvaluate(roll, config, message);
|
||||||
|
config.roll.hope = {
|
||||||
await setDiceSoNiceForDualityRoll(
|
|
||||||
roll,
|
|
||||||
config.roll.advantage.type,
|
|
||||||
config.roll.hope.dice,
|
|
||||||
config.roll.fear.dice,
|
|
||||||
config.roll.advantage.dice
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
|
||||||
const data = super.postEvaluate(roll, config);
|
|
||||||
|
|
||||||
data.hope = {
|
|
||||||
dice: roll.dHope.denomination,
|
dice: roll.dHope.denomination,
|
||||||
value: this.guaranteedCritical ? 0 : roll.dHope.total,
|
value: this.guaranteedCritical ? 0 : roll.dHope.total,
|
||||||
rerolled: {
|
rerolled: {
|
||||||
|
|
@ -255,7 +243,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
rerolls: roll.dHope.results.filter(x => x.rerolled)
|
rerolls: roll.dHope.results.filter(x => x.rerolled)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
data.fear = {
|
config.roll.fear = {
|
||||||
dice: roll.dFear.denomination,
|
dice: roll.dFear.denomination,
|
||||||
value: this.guaranteedCritical ? 0 : roll.dFear.total,
|
value: this.guaranteedCritical ? 0 : roll.dFear.total,
|
||||||
rerolled: {
|
rerolled: {
|
||||||
|
|
@ -263,11 +251,11 @@ export default class DualityRoll extends D20Roll {
|
||||||
rerolls: roll.dFear.results.filter(x => x.rerolled)
|
rerolls: roll.dFear.results.filter(x => x.rerolled)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
data.rally = {
|
config.roll.rally = {
|
||||||
dice: roll.dRally?.denomination,
|
dice: roll.dRally?.denomination,
|
||||||
value: roll.dRally?.total
|
value: roll.dRally?.total
|
||||||
};
|
};
|
||||||
data.result = {
|
config.roll.result = {
|
||||||
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
|
||||||
total: this.guaranteedCritical ? 0 : roll.dHope.total + roll.dFear.total,
|
total: this.guaranteedCritical ? 0 : roll.dHope.total + roll.dFear.total,
|
||||||
label: roll.totalLabel
|
label: roll.totalLabel
|
||||||
|
|
@ -275,11 +263,18 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
if (roll._rallyIndex && roll.data?.parent)
|
if (roll._rallyIndex && roll.data?.parent)
|
||||||
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
|
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
static async buildPost(roll, config, message) {
|
static async buildPost(roll, config, message) {
|
||||||
|
await setDiceSoNiceForDualityRoll(
|
||||||
|
roll,
|
||||||
|
config.roll.advantage.type,
|
||||||
|
config.roll.hope.dice,
|
||||||
|
config.roll.fear.dice,
|
||||||
|
config.roll.advantage.dice
|
||||||
|
);
|
||||||
|
|
||||||
await super.buildPost(roll, config, message);
|
await super.buildPost(roll, config, message);
|
||||||
|
|
||||||
await DualityRoll.dualityUpdate(config);
|
await DualityRoll.dualityUpdate(config);
|
||||||
|
|
@ -414,8 +409,4 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
return rerolled;
|
return rerolled;
|
||||||
}
|
}
|
||||||
|
|
||||||
fromJSON(json) {
|
|
||||||
return super.fromJSON(json);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,25 +75,23 @@ export default class FateRoll extends D20Roll {
|
||||||
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
|
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
await super.buildEvaluate(roll, config, message);
|
await super.buildEvaluate(roll, config, message);
|
||||||
|
config.roll.fate = {
|
||||||
|
dice: roll.fateDie === 'Hope' ? roll.dHope.denomination : roll.dFear.denomination,
|
||||||
|
value: roll.fateDie === 'Hope' ? roll.dHope.total : roll.dFear.total,
|
||||||
|
fateDie: roll.fateDie
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static async buildPost(roll, config, message) {
|
||||||
if (roll.fateDie === 'Hope') {
|
if (roll.fateDie === 'Hope') {
|
||||||
await setDiceSoNiceForHopeFateRoll(roll, config.roll.fate.dice);
|
await setDiceSoNiceForHopeFateRoll(roll, config.roll.fate.dice);
|
||||||
} else {
|
} else {
|
||||||
await setDiceSoNiceForFearFateRoll(roll, config.roll.fate.dice);
|
await setDiceSoNiceForFearFateRoll(roll, config.roll.fate.dice);
|
||||||
}
|
}
|
||||||
}
|
return super.buildPost(roll, config, message);
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
|
||||||
const data = super.postEvaluate(roll, config);
|
|
||||||
|
|
||||||
data.fate = {
|
|
||||||
dice: roll.fateDie === 'Hope' ? roll.dHope.denomination : roll.dFear.denomination,
|
|
||||||
value: roll.fateDie === 'Hope' ? roll.dHope.total : roll.dFear.total,
|
|
||||||
fateDie: roll.fateDie
|
|
||||||
};
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
module/documents/_types.d.ts
vendored
13
module/documents/_types.d.ts
vendored
|
|
@ -3,17 +3,26 @@ import DHItem from './item.mjs';
|
||||||
import BaseDataItem from '../data/item/base.mjs';
|
import BaseDataItem from '../data/item/base.mjs';
|
||||||
import DhActiveEffect from './activeEffect.mjs';
|
import DhActiveEffect from './activeEffect.mjs';
|
||||||
import EmbeddedCollection from '@common/abstract/embedded-collection.mjs';
|
import EmbeddedCollection from '@common/abstract/embedded-collection.mjs';
|
||||||
|
import DHToken from './token.mjs';
|
||||||
|
import Actor from '@client/documents/actor.mjs';
|
||||||
|
import Item from '@client/documents/item.mjs';
|
||||||
|
|
||||||
declare module './actor.mjs' {
|
declare module './actor.mjs' {
|
||||||
export default interface DhpActor<T extends BaseDataActor = BaseDataActor> {
|
export default interface DhpActor<T extends BaseDataActor = BaseDataActor> extends Actor {
|
||||||
system: T;
|
system: T;
|
||||||
items: EmbeddedCollection<DHItem>;
|
items: EmbeddedCollection<DHItem>;
|
||||||
effects: EmbeddedCollection<DhActiveEffect>;
|
effects: EmbeddedCollection<DhActiveEffect>;
|
||||||
|
get token(): DHToken | null;
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
getActiveTokens(linked?: boolean, document?: boolean): (DHToken | foundry.canvas.placeables.Token)[];
|
||||||
|
getActiveTokens(linked?: boolean, document: true): DHToken[];
|
||||||
|
getActiveTokens(linked?: boolean, document: false): foundry.canvas.placeables.Token[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module './item.mjs' {
|
declare module './item.mjs' {
|
||||||
export default interface DHItem<T extends BaseDataItem = BaseDataItem> {
|
export default interface DHItem<T extends BaseDataItem = BaseDataItem> extends Item {
|
||||||
parent: DhpActor;
|
parent: DhpActor;
|
||||||
actor: DhpActor;
|
actor: DhpActor;
|
||||||
system: T;
|
system: T;
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasDescription() {
|
||||||
|
return Boolean(this.description);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Event Handlers */
|
/* Event Handlers */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,14 @@ export default class DhpActor extends Actor {
|
||||||
super.prepareData();
|
super.prepareData();
|
||||||
|
|
||||||
// Update effects if it is the user's character or is controlled
|
// Update effects if it is the user's character or is controlled
|
||||||
if (canvas.ready) {
|
// A timeout avoids an infinite loop when accessing token actors before the delta is finished constructing
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if (!canvas.ready) return;
|
||||||
const controlled = canvas.tokens.controlled.some(t => t.actor === this);
|
const controlled = canvas.tokens.controlled.some(t => t.actor === this);
|
||||||
if (game.user.character === this || controlled) {
|
if (game.user.character === this || controlled) {
|
||||||
ui.effectsDisplay.render();
|
ui.effectsDisplay.refresh();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -620,53 +622,63 @@ export default class DhpActor extends Actor {
|
||||||
return rollData;
|
return rollData;
|
||||||
}
|
}
|
||||||
|
|
||||||
#canReduceDamage(hpDamage, type) {
|
/**
|
||||||
const { stressDamageReduction, disabledArmor } = this.system.rules.damageReduction;
|
* Checks to see if damage can be reduced in one way or another.
|
||||||
|
* @param {number} hpDamage the amount of marked hp that will be marked
|
||||||
|
* @param {Set<string>} types a list of damage types
|
||||||
|
*/
|
||||||
|
#canReduceDamage(hpDamage, types) {
|
||||||
|
const { stressDamageReduction, disabledArmor, reduceSeverity, thresholdImmunities } =
|
||||||
|
this.system.rules.damageReduction;
|
||||||
if (disabledArmor) return false;
|
if (disabledArmor) return false;
|
||||||
|
|
||||||
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
|
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
|
||||||
|
|
||||||
const canUseArmor =
|
const canUseArmor =
|
||||||
this.system.armorScore.value < this.system.armorScore.max &&
|
this.system.armorScore.value < this.system.armorScore.max &&
|
||||||
type.every(t => this.system.armorApplicableDamageTypes[t] === true);
|
types.every(t => this.system.armorApplicableDamageTypes[t] === true);
|
||||||
|
|
||||||
const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => {
|
const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => {
|
||||||
const rule = stressDamageReduction[x];
|
const rule = stressDamageReduction[x];
|
||||||
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
|
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
|
||||||
return acc;
|
return acc;
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
return canUseArmor || canUseStress;
|
const hasReduceSeverity = types.some(t => reduceSeverity[t]);
|
||||||
|
|
||||||
|
const hasThresholdImmunity = Object.entries(thresholdImmunities)
|
||||||
|
.filter(([key, value]) => Boolean(value) && damageKeyToNumber(key) === hpDamage)
|
||||||
|
.length;
|
||||||
|
|
||||||
|
return canUseArmor || canUseStress || hasReduceSeverity || hasThresholdImmunity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeDamage(damages, isDirect = false) {
|
async takeDamage(args, isDirect = false) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === false) return null;
|
args = this.#parseDamageArgs(args);
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, args) === false) return null;
|
||||||
|
|
||||||
if (this.type === 'companion') {
|
if (this.type === 'companion') {
|
||||||
await this.modifyResource([{ value: 1, key: 'stress' }]);
|
await this.modifyResource([{ value: 1, key: 'stress' }]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const updates = [];
|
const updates = args.resourceUpdates;
|
||||||
|
if (args.main) {
|
||||||
|
// todo: avoid side effects, but hook currently requires it
|
||||||
|
args.main.value = this.calculateDamage(args.main.value, args.main.damageTypes);
|
||||||
|
}
|
||||||
|
|
||||||
Object.entries(damages).forEach(([key, damage]) => {
|
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, args) === false) return null;
|
||||||
damage.parts.forEach(part => {
|
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
|
|
||||||
part.total = this.calculateDamage(part.total, part.damageTypes);
|
|
||||||
const update = updates.find(u => u.key === key);
|
|
||||||
if (update) {
|
|
||||||
update.value += part.total;
|
|
||||||
update.damageTypes.add(...new Set(part.damageTypes));
|
|
||||||
} else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
|
// Convert deducted resources to a record of updates. Return if nothing to do.
|
||||||
|
if (!updates.some(u => u.value) && !args.main) return;
|
||||||
|
|
||||||
if (!updates.length) return;
|
if (args.main) {
|
||||||
|
const hpDamage = {
|
||||||
const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
value: this.convertDamageToThreshold(args.main.value),
|
||||||
if (hpDamage?.value) {
|
damageTypes: new Set(args.main.damageTypes),
|
||||||
hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
|
key: CONFIG.DH.GENERAL.healingTypes.hitPoints.id
|
||||||
|
};
|
||||||
if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
|
if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
|
||||||
const armorSlotResult = await this.owner.query(
|
const armorSlotResult = await this.owner.query(
|
||||||
'armorSlot',
|
'armorSlot',
|
||||||
|
|
@ -681,7 +693,7 @@ export default class DhpActor extends Actor {
|
||||||
);
|
);
|
||||||
if (armorSlotResult) {
|
if (armorSlotResult) {
|
||||||
const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult;
|
const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult;
|
||||||
updates.find(u => u.key === 'hitPoints').value = modifiedDamage;
|
hpDamage.value = modifiedDamage;
|
||||||
for (const armorChange of armorChanges) {
|
for (const armorChange of armorChanges) {
|
||||||
updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid });
|
updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid });
|
||||||
}
|
}
|
||||||
|
|
@ -691,20 +703,24 @@ export default class DhpActor extends Actor {
|
||||||
else updates.push({ value: stressSpent, key: 'stress' });
|
else updates.push({ value: stressSpent, key: 'stress' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (this.type === 'adversary') {
|
||||||
if (this.type === 'adversary') {
|
|
||||||
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
|
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
|
||||||
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
|
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
|
||||||
}, 0);
|
}, 0);
|
||||||
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
|
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
|
||||||
|
if (this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]) {
|
||||||
if (
|
hpDamage.value = Math.max(0, hpDamage.value - 1);
|
||||||
hpDamage.value &&
|
|
||||||
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
|
|
||||||
) {
|
|
||||||
hpDamage.value -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge existing hitPoint deduction with finalised damage deduction
|
||||||
|
const existing = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
|
||||||
|
if (existing) {
|
||||||
|
existing.value += hpDamage.value;
|
||||||
|
existing.damageTypes = hpDamage.damageTypes;
|
||||||
|
} else {
|
||||||
|
updates.push(hpDamage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await game.system.registeredTriggers.runTrigger(
|
const results = await game.system.registeredTriggers.runTrigger(
|
||||||
|
|
@ -720,11 +736,9 @@ export default class DhpActor extends Actor {
|
||||||
resourceMap.updateResources();
|
resourceMap.updateResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
updates.forEach(
|
for (const u of updates) {
|
||||||
u =>
|
u.value = u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value;
|
||||||
(u.value =
|
}
|
||||||
u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.modifyResource(updates);
|
await this.modifyResource(updates);
|
||||||
|
|
||||||
|
|
@ -733,6 +747,36 @@ export default class DhpActor extends Actor {
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async takeHealing(args) {
|
||||||
|
args = this.#parseDamageArgs({ resources: 'resources' in args ? args.resources : args });
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, args) === false) return null;
|
||||||
|
|
||||||
|
const updates = args.resourceUpdates;
|
||||||
|
for (const u of updates) {
|
||||||
|
const shouldFlip = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false);
|
||||||
|
u.value = shouldFlip ? u.value * -1 : u.value;
|
||||||
|
}
|
||||||
|
await this.modifyResource(updates);
|
||||||
|
|
||||||
|
if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null;
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse damage args that may be coming from takeHealing or takeDamage. Used to simplify macro usage and roll vs non-roll usage */
|
||||||
|
#parseDamageArgs(args = {}) {
|
||||||
|
const damageRoll = 'total' in args ? args : (args.main ?? args.damage);
|
||||||
|
const damageValue = typeof damageRoll === 'number' ? damageRoll : damageRoll?.total;
|
||||||
|
const damageTypes = Array.from(damageRoll?.options?.damageTypes ?? damageRoll?.damageTypes ?? []);
|
||||||
|
const main = typeof damageValue === 'number' ? { key: 'damage', value: damageValue, damageTypes } : null;
|
||||||
|
const resourceUpdates = Object.entries(args.resources ?? {}).map(([key, damage]) => ({
|
||||||
|
key,
|
||||||
|
value: typeof damage === 'number' ? damage : damage?.total ?? 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { main, resourceUpdates };
|
||||||
|
}
|
||||||
|
|
||||||
calculateDamage(baseDamage, type) {
|
calculateDamage(baseDamage, type) {
|
||||||
if (this.canResist(type, 'immunity')) return 0;
|
if (this.canResist(type, 'immunity')) return 0;
|
||||||
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
|
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
|
||||||
|
|
@ -757,32 +801,6 @@ export default class DhpActor extends Actor {
|
||||||
return reduction === Infinity ? 0 : reduction;
|
return reduction === Infinity ? 0 : reduction;
|
||||||
}
|
}
|
||||||
|
|
||||||
async takeHealing(healings) {
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeHealing`, this, healings) === false) return null;
|
|
||||||
|
|
||||||
const updates = [];
|
|
||||||
Object.entries(healings).forEach(([key, healing]) => {
|
|
||||||
healing.parts.forEach(part => {
|
|
||||||
const update = updates.find(u => u.key === key);
|
|
||||||
if (update) update.value += part.total;
|
|
||||||
else updates.push({ value: part.total, key });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
updates.forEach(
|
|
||||||
u =>
|
|
||||||
(u.value = !(u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false)
|
|
||||||
? u.value * -1
|
|
||||||
: u.value)
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.modifyResource(updates);
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null;
|
|
||||||
|
|
||||||
return updates;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resources are modified asynchronously, so be careful not to update the same resource in
|
* Resources are modified asynchronously, so be careful not to update the same resource in
|
||||||
* quick succession.
|
* quick succession.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,13 @@ import { emitGMUpdate, emitGMCreate, GMUpdateEvent } from '../systemRegistration
|
||||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
targetHook = null;
|
targetHook = null;
|
||||||
|
|
||||||
|
static #EXPAND_SECTIONS = [
|
||||||
|
{ selector: 'roll-section [data-action="expandRoll"]', key: 'roll' },
|
||||||
|
{ selector: 'damage-section', key: 'damage' },
|
||||||
|
{ selector: 'target-section', key: 'target' },
|
||||||
|
{ selector: 'description-section', key: 'desc' }
|
||||||
|
];
|
||||||
|
|
||||||
async renderHTML() {
|
async renderHTML() {
|
||||||
const actor = game.actors.get(this.speaker.actor);
|
const actor = game.actors.get(this.speaker.actor);
|
||||||
const actorData =
|
const actorData =
|
||||||
|
|
@ -89,23 +96,26 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check registered selectors and the main item section for expanding
|
||||||
|
// Preserving during re-render is handled by core foundry on anything with [data-action=expandRoll]
|
||||||
const autoExpandRoll = game.settings.get(
|
const autoExpandRoll = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
).expandRollMessage;
|
||||||
).expandRollMessage,
|
for (const { selector, key } of DhpChatMessage.#EXPAND_SECTIONS) {
|
||||||
rollSections = html.querySelectorAll('.roll-part'),
|
const elements = html.querySelectorAll(selector);
|
||||||
itemDesc = html.querySelector('.domain-card-move');
|
for (const element of elements) {
|
||||||
rollSections.forEach(s => {
|
element.classList.toggle('expanded', autoExpandRoll[key]);
|
||||||
if (s.classList.contains('roll-section')) {
|
}
|
||||||
const toExpand = s.querySelector('[data-action="expandRoll"]');
|
}
|
||||||
toExpand.classList.toggle('expanded', autoExpandRoll.roll);
|
|
||||||
} else if (s.classList.contains('damage-section'))
|
// Auto expand the item description. These are not preserved by foundry during re-renders
|
||||||
s.classList.toggle('expanded', autoExpandRoll.damage);
|
const itemDesc = html.querySelector('details');
|
||||||
else if (s.classList.contains('target-section')) s.classList.toggle('expanded', autoExpandRoll.target);
|
if (itemDesc) {
|
||||||
else if (s.classList.contains('description-section'))
|
const existing = document.querySelector(`.chat-message[data-message-id="${this.id}"] details`);
|
||||||
s.classList.toggle('expanded', autoExpandRoll.desc);
|
if (existing?.hasAttribute('open') ?? autoExpandRoll.desc) {
|
||||||
});
|
itemDesc.setAttribute('open', '');
|
||||||
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
||||||
|
|
@ -276,8 +286,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
async onCreateAreas(event) {
|
async onCreateAreas(event) {
|
||||||
const createArea = async selectedArea => {
|
const createArea = async selectedArea => {
|
||||||
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
|
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
|
||||||
const { shape: type, size: range } = selectedArea;
|
const { shape: shapeType, size: range, hasHole } = selectedArea;
|
||||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
|
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
||||||
|
shapeType,
|
||||||
|
range,
|
||||||
|
hasHole
|
||||||
|
});
|
||||||
|
|
||||||
const scene = game.scenes.get(game.user.viewedScene);
|
const scene = game.scenes.get(game.user.viewedScene);
|
||||||
const level = scene.levels.find(x => x.isView);
|
const level = scene.levels.find(x => x.isView);
|
||||||
|
|
@ -305,7 +319,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
||||||
};
|
};
|
||||||
const placeRegion = data => {
|
const placeRegion = data => {
|
||||||
canvas.regions.placeRegion(data, { create: true });
|
canvas.regions.placeRegion(data, {
|
||||||
|
create: true,
|
||||||
|
attachToToken: selectedArea.type === CONFIG.DH.ACTIONS.areaTypes.attached.id
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Regions with effects must be placed by the GM
|
// Regions with effects must be placed by the GM
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,10 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
return !pack?.locked && this.isOwner && isValidType && hasActions;
|
return !pack?.locked && this.isOwner && isValidType && hasActions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasDescription() {
|
||||||
|
return Boolean(this.system.description) || Boolean(this.system.itemFeatures?.length);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
||||||
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
||||||
|
|
@ -204,10 +208,7 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
tags: this._getTags()
|
tags: this._getTags()
|
||||||
},
|
},
|
||||||
actions: item.system.actionsList,
|
actions: item.system.actionsList,
|
||||||
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.system.description, {
|
description: await this.system.getEnrichedDescription()
|
||||||
relativeTo: this.parent,
|
|
||||||
rollData: this.parent?.getRollData() ?? {}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
||||||
tokens.filter(x => x.actor).map(x => x.actor)
|
tokens.filter(x => x.actor).map(x => x.actor)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
super.createCombatants(tokens, combat ?? {});
|
|
||||||
|
await super.createCombatants(tokens, combat ?? {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,10 @@ export default class DhTokenManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await canvas.tokens.placeTokens(
|
const placedData = await canvas.tokens.placeTokens(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
...actor.prototypeToken.toObject(),
|
...(await actor.getTokenDocument()).toObject(),
|
||||||
actorId: actor.id,
|
actorId: actor.id,
|
||||||
displayName: 50,
|
displayName: 50,
|
||||||
...tokenData
|
...tokenData
|
||||||
|
|
@ -30,6 +30,8 @@ export default class DhTokenManager {
|
||||||
],
|
],
|
||||||
{ create: false }
|
{ create: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return placedData?.[0] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,22 +48,24 @@ export default class DhTokenManager {
|
||||||
|
|
||||||
const createElevation = elevation ?? level.elevation.bottom;
|
const createElevation = elevation ?? level.elevation.bottom;
|
||||||
for (const tokenData of tokensData) {
|
for (const tokenData of tokensData) {
|
||||||
const previewTokens = await this.createPreview(tokenData.actor, {
|
const previewToken = await this.createPreview(tokenData.actor, {
|
||||||
name: tokenData.tokenPreviewName,
|
name: tokenData.tokenPreviewName,
|
||||||
level: game.user.viewedLevel,
|
level: game.user.viewedLevel,
|
||||||
elevation: createElevation,
|
elevation: createElevation,
|
||||||
flags: { daggerheart: { createPlacement: true } }
|
flags: { daggerheart: { createPlacement: true } }
|
||||||
});
|
});
|
||||||
if (!previewTokens?.length) return null;
|
if (!previewToken) return null;
|
||||||
|
|
||||||
await canvas.scene.createEmbeddedDocuments(
|
const finalTokenData = {
|
||||||
'Token',
|
...previewToken.toObject(),
|
||||||
previewTokens.map(x => ({
|
|
||||||
...x.toObject(),
|
|
||||||
name: tokenData.actor.prototypeToken.name,
|
name: tokenData.actor.prototypeToken.name,
|
||||||
displayName: tokenData.actor.prototypeToken.displayName,
|
displayName: tokenData.actor.prototypeToken.displayName,
|
||||||
flags: tokenData.actor.prototypeToken.flags
|
flags: tokenData.actor.prototypeToken.flags
|
||||||
})),
|
};
|
||||||
|
|
||||||
|
await canvas.scene.createEmbeddedDocuments(
|
||||||
|
'Token',
|
||||||
|
[finalTokenData],
|
||||||
{ controlObject: true, parent: canvas.scene }
|
{ controlObject: true, parent: canvas.scene }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,55 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
#wide = false;
|
#wide = false;
|
||||||
#bordered = false;
|
#bordered = false;
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
async activate(element, options = {}) {
|
async activate(element, options = {}) {
|
||||||
const { TextEditor } = foundry.applications.ux;
|
this.#wide = false;
|
||||||
|
this.#bordered = false;
|
||||||
|
|
||||||
|
const isMacro = document.getElementById('action-bar').contains(element);
|
||||||
|
const macro = isMacro ? game.macros.get(game.user.hotbar[Number(element.dataset.slot)] ?? null) : null;
|
||||||
|
const macroItemUuid = macro?.type === 'script' ? macro.command.match(/await game\.system\.api\.applications\.ui\.DhHotbar\.useItem\("([^"]+)"\);/)?.[1] : null;
|
||||||
|
if (macroItemUuid && await fromUuid(macroItemUuid, { strict: false })) {
|
||||||
|
element.dataset.tooltip = `#item#${macroItemUuid}`;
|
||||||
|
options.direction = this.constructor.TOOLTIP_DIRECTIONS.UP;
|
||||||
|
}
|
||||||
|
|
||||||
let html = options.html;
|
let html = options.html;
|
||||||
if (element.dataset.tooltip?.startsWith('#battlepoints#')) {
|
const key = element.dataset.tooltip?.match(/^#([\w-]+)#/)?.[1];
|
||||||
|
switch (key) {
|
||||||
|
case 'battlepoints':
|
||||||
|
return this.#activateBattlepoints(element, options);
|
||||||
|
case 'effect-display':
|
||||||
|
html = await this.#activateEffectDisplay(element, options);
|
||||||
|
break;
|
||||||
|
case 'item':
|
||||||
|
html = await this.#activateItem(element, options);
|
||||||
|
break;
|
||||||
|
case 'attack':
|
||||||
|
html = await this.#activateAttack(element, options);
|
||||||
|
break;
|
||||||
|
case 'shortRest':
|
||||||
|
case 'longRest':
|
||||||
|
html = await this.#activateRest(element, options);
|
||||||
|
break;
|
||||||
|
case 'advantage':
|
||||||
|
case 'disadvantage':
|
||||||
|
html = await this.#activateAdvantageDisadvantage(element, options);
|
||||||
|
break;
|
||||||
|
case 'deathMove':
|
||||||
|
html = await this.#activateDeathMove(element, options);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.noOffset = options.noOffset;
|
||||||
|
super.activate(element, { ...options, html });
|
||||||
|
}
|
||||||
|
|
||||||
|
async #activateBattlepoints(element, options) {
|
||||||
this.#wide = true;
|
this.#wide = true;
|
||||||
this.#bordered = true;
|
this.#bordered = true;
|
||||||
|
|
||||||
html = await this.getBattlepointHTML(element.dataset.combatId);
|
const html = await this.getBattlepointHTML(element.dataset.combatId);
|
||||||
options.direction = this._determineItemTooltipDirection(element);
|
options.direction = this._determineItemTooltipDirection(element);
|
||||||
super.activate(element, { ...options, html: html });
|
super.activate(element, { ...options, html: html });
|
||||||
|
|
||||||
|
|
@ -20,13 +60,9 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
|
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
|
||||||
element.addEventListener('input', this.toggleModifier.bind(this));
|
element.addEventListener('input', this.toggleModifier.bind(this));
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.#wide = false;
|
|
||||||
this.#bordered = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.dataset.tooltip === '#effect-display#') {
|
async #activateEffectDisplay(element, options) {
|
||||||
this.#bordered = true;
|
this.#bordered = true;
|
||||||
let effect = {};
|
let effect = {};
|
||||||
if (element.dataset.uuid) {
|
if (element.dataset.uuid) {
|
||||||
|
|
@ -75,7 +111,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
|
||||||
{
|
{
|
||||||
effect
|
effect
|
||||||
|
|
@ -84,11 +120,11 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
this.tooltip.innerHTML = html;
|
||||||
options.direction = this._determineItemTooltipDirection(element);
|
options.direction = this._determineItemTooltipDirection(element);
|
||||||
} else {
|
|
||||||
this.#bordered = false;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (element.dataset.tooltip?.startsWith('#item#')) {
|
async #activateItem(element, options) {
|
||||||
const itemUuid = element.dataset.tooltip.slice(6);
|
const itemUuid = element.dataset.tooltip.slice(6);
|
||||||
const item = await foundry.utils.fromUuid(itemUuid);
|
const item = await foundry.utils.fromUuid(itemUuid);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|
@ -97,7 +133,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
await this.enrichText(item);
|
await this.enrichText(item);
|
||||||
|
|
||||||
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
const type = isAction ? 'action' : isEffect ? 'effect' : item.type;
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
||||||
{
|
{
|
||||||
item: item,
|
item: item,
|
||||||
|
|
@ -108,17 +144,20 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
);
|
);
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
this.tooltip.innerHTML = html;
|
||||||
options.direction = this._determineItemTooltipDirection(element);
|
options.direction ??= this._determineItemTooltipDirection(element);
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const attack = element.dataset.tooltip?.startsWith('#attack#');
|
return null;
|
||||||
if (attack) {
|
}
|
||||||
|
|
||||||
|
async #activateAttack(element, options) {
|
||||||
const actorUuid = element.dataset.tooltip.slice(8);
|
const actorUuid = element.dataset.tooltip.slice(8);
|
||||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||||
const attack = actor.system.attack;
|
const attack = actor.system.attack;
|
||||||
|
|
||||||
const description = await TextEditor.enrichHTML(attack.description);
|
const description = await foundry.applications.ux.TextEditor.enrichHTML(attack.description);
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/attack.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/attack.hbs`,
|
||||||
{
|
{
|
||||||
attack: attack,
|
attack: attack,
|
||||||
|
|
@ -129,19 +168,57 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
);
|
);
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
this.tooltip.innerHTML = html;
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shortRest = element.dataset.tooltip?.startsWith('#shortRest#');
|
async #activateAdvantageDisadvantage(element, options) {
|
||||||
const longRest = element.dataset.tooltip?.startsWith('#longRest#');
|
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
|
||||||
if (shortRest || longRest) {
|
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
|
||||||
const key = element.dataset.tooltip.slice(shortRest ? 11 : 10);
|
const actor = await foundry.utils.fromUuid(actorUuid);
|
||||||
|
|
||||||
|
if (actor) {
|
||||||
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
|
||||||
|
{
|
||||||
|
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tooltip.innerHTML = html;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #activateDeathMove(element, options) {
|
||||||
|
const name = element.dataset.deathName;
|
||||||
|
const img = element.dataset.deathImg;
|
||||||
|
const description = element.dataset.deathDescription;
|
||||||
|
|
||||||
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/death-move.hbs`,
|
||||||
|
{
|
||||||
|
move: { name: name, img: img, description: description }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tooltip.innerHTML = html;
|
||||||
|
options.direction = this._determineItemTooltipDirection(
|
||||||
|
element,
|
||||||
|
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
||||||
|
);
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
async #activateRest(element, options) {
|
||||||
|
const isShortRest = element.dataset.tooltip?.startsWith('#shortRest#');
|
||||||
|
const key = element.dataset.tooltip.slice(isShortRest ? 11 : 10);
|
||||||
const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[
|
const moves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves[
|
||||||
element.dataset.restType
|
element.dataset.restType
|
||||||
].moves;
|
].moves;
|
||||||
const move = moves[key];
|
const move = moves[key];
|
||||||
const description = await TextEditor.enrichHTML(move.description);
|
const description = await foundry.applications.ux.TextEditor.enrichHTML(move.description);
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
const html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
|
||||||
{
|
{
|
||||||
move: move,
|
move: move,
|
||||||
|
|
@ -154,49 +231,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
element,
|
element,
|
||||||
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
||||||
);
|
);
|
||||||
}
|
return html;
|
||||||
|
|
||||||
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
|
|
||||||
const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#');
|
|
||||||
if (isAdvantage || isDisadvantage) {
|
|
||||||
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
|
|
||||||
const actor = await foundry.utils.fromUuid(actorUuid);
|
|
||||||
|
|
||||||
if (actor) {
|
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
|
|
||||||
{
|
|
||||||
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deathMove = element.dataset.tooltip?.startsWith('#deathMove#');
|
|
||||||
if (deathMove) {
|
|
||||||
const name = element.dataset.deathName;
|
|
||||||
const img = element.dataset.deathImg;
|
|
||||||
const description = element.dataset.deathDescription;
|
|
||||||
|
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
|
||||||
`systems/daggerheart/templates/ui/tooltip/death-move.hbs`,
|
|
||||||
{
|
|
||||||
move: { name: name, img: img, description: description }
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tooltip.innerHTML = html;
|
|
||||||
options.direction = this._determineItemTooltipDirection(
|
|
||||||
element,
|
|
||||||
this.constructor.TOOLTIP_DIRECTIONS.RIGHT
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.noOffset = options.noOffset;
|
|
||||||
super.activate(element, { ...options, html: html });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setAnchor(direction) {
|
_setAnchor(direction) {
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,11 @@ export const renderMeasuredTemplate = async event => {
|
||||||
if (!type || !range || !game.canvas.scene) return;
|
if (!type || !range || !game.canvas.scene) return;
|
||||||
|
|
||||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
||||||
type,
|
shapeType: type,
|
||||||
angle,
|
angle,
|
||||||
range,
|
range,
|
||||||
direction
|
direction,
|
||||||
|
hasHole: false
|
||||||
});
|
});
|
||||||
|
|
||||||
await canvas.regions.placeRegion(
|
await canvas.regions.placeRegion(
|
||||||
|
|
|
||||||
|
|
@ -35,23 +35,24 @@ export const enricherConfig = [
|
||||||
];
|
];
|
||||||
|
|
||||||
export const enricherRenderSetup = element => {
|
export const enricherRenderSetup = element => {
|
||||||
|
const clickWrapper = func => event => {
|
||||||
|
event.stopPropagation();
|
||||||
|
func(event);
|
||||||
|
};
|
||||||
|
|
||||||
element
|
element
|
||||||
.querySelectorAll('.enriched-damage-button')
|
.querySelectorAll('.enriched-damage-button')
|
||||||
.forEach(element => element.addEventListener('click', renderDamageButton));
|
.forEach(element => element.addEventListener('click', clickWrapper(renderDamageButton)));
|
||||||
|
|
||||||
element
|
element
|
||||||
.querySelectorAll('.duality-roll-button')
|
.querySelectorAll('.duality-roll-button')
|
||||||
.forEach(element => element.addEventListener('click', renderDualityButton));
|
.forEach(element => element.addEventListener('click', clickWrapper(renderDualityButton)));
|
||||||
|
|
||||||
element
|
element
|
||||||
.querySelectorAll('.fate-roll-button')
|
.querySelectorAll('.fate-roll-button')
|
||||||
.forEach(element => element.addEventListener('click', renderFateButton));
|
.forEach(element => element.addEventListener('click', clickWrapper(renderFateButton)));
|
||||||
|
|
||||||
element
|
element
|
||||||
.querySelectorAll('.measured-template-button')
|
.querySelectorAll('.measured-template-button')
|
||||||
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
|
.forEach(element => element.addEventListener('click', clickWrapper(renderMeasuredTemplate)));
|
||||||
|
|
||||||
// element
|
|
||||||
// .querySelectorAll('.enriched-effect')
|
|
||||||
// .forEach(element => element.addEventListener('dragstart', dragEnrichedEffect));
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,8 @@ export default class RegisterHandlebarsHelpers {
|
||||||
return formula;
|
return formula;
|
||||||
}
|
}
|
||||||
|
|
||||||
static damageSymbols(damageParts) {
|
static damageSymbols(damageData) {
|
||||||
const allTypes = [...new Set([...damageParts].flatMap(x => Array.from(x.type)))];
|
const symbols = damageData.type.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
|
||||||
const symbols = allTypes.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
|
|
||||||
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
|
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs';
|
import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs';
|
||||||
import Tagify from '@yaireo/tagify';
|
import Tagify from '@yaireo/tagify';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @import DhpActor from '../documents/actor.mjs';
|
||||||
|
*/
|
||||||
|
|
||||||
export const capitalize = string => {
|
export const capitalize = string => {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
};
|
};
|
||||||
|
|
@ -696,19 +700,6 @@ export async function RefreshFeatures(
|
||||||
return refreshedActors;
|
return refreshedActors;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUnusedDamageTypes(parts) {
|
|
||||||
const usedKeys = Object.keys(parts);
|
|
||||||
return Object.keys(CONFIG.DH.GENERAL.healingTypes).reduce((acc, key) => {
|
|
||||||
if (!usedKeys.includes(key))
|
|
||||||
acc.push({
|
|
||||||
value: key,
|
|
||||||
label: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label)
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns resolved armor sources ordered by application order */
|
/** Returns resolved armor sources ordered by application order */
|
||||||
export function getArmorSources(actor) {
|
export function getArmorSources(actor) {
|
||||||
const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData);
|
const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData);
|
||||||
|
|
@ -890,3 +881,32 @@ export function shouldUseHopeFearAutomation(options = { gmAsPlayer: true }) {
|
||||||
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm;
|
return (!game.user.isGM || options.gmAsPlayer) ? hopeFear.players : hopeFear.gm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the given actor if its a world actor,
|
||||||
|
* finds a world actor equivalent,
|
||||||
|
* or imports the actor and returns the imported actor.
|
||||||
|
* @param {DhpActor} baseActor
|
||||||
|
* @returns {Promise<DhpActor>} a world actor
|
||||||
|
*/
|
||||||
|
export async function getWorldActor(baseActor) {
|
||||||
|
if (baseActor.inCompendium) {
|
||||||
|
const worldActorCandidates = game.actors.filter(x =>
|
||||||
|
x._stats.compendiumSource === baseActor.uuid &&
|
||||||
|
x.prototypeToken.actorLink === baseActor.prototypeToken.actorLink
|
||||||
|
);
|
||||||
|
const worldActorCopy = worldActorCandidates.find(a => a.name === baseActor.name) ?? worldActorCandidates[0];
|
||||||
|
if (worldActorCopy) return worldActorCopy;
|
||||||
|
|
||||||
|
const baseActorData = baseActor;
|
||||||
|
return await game.system.api.documents.DhpActor.create({
|
||||||
|
...baseActorData,
|
||||||
|
_stats: {
|
||||||
|
...baseActorData._stats,
|
||||||
|
compendiumSource: baseActor.uuid
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseActor;
|
||||||
|
}
|
||||||
39
module/systemRegistration/migration-handlers/2_5_2.mjs
Normal file
39
module/systemRegistration/migration-handlers/2_5_2.mjs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { MigrationHandlerBase } from './base.mjs';
|
||||||
|
|
||||||
|
export class Migration_2_5_2 extends MigrationHandlerBase {
|
||||||
|
version = '2.5.2';
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async updateActiveEffectSource(effectSource, item) {
|
||||||
|
let shouldUpdate = false;
|
||||||
|
const newChanges = [];
|
||||||
|
|
||||||
|
const srdItem = item?._stats?.compendiumSource ?
|
||||||
|
await foundry.utils.fromUuid(item?._stats.compendiumSource) :
|
||||||
|
null;
|
||||||
|
for (let i = 0; i < effectSource.system.changes.length; i++) {
|
||||||
|
const change = effectSource.system.changes[i];
|
||||||
|
const srdEffect = srdItem?.effects.find(x => x.name === effectSource.name);
|
||||||
|
if (change.type === 'custom') {
|
||||||
|
const srdChange = srdEffect ? srdEffect.system.changes[i] : null;
|
||||||
|
if (srdChange &&
|
||||||
|
change.key === srdChange.key &&
|
||||||
|
change.value === srdChange.value &&
|
||||||
|
change.type !== srdChange.type
|
||||||
|
) {
|
||||||
|
shouldUpdate = true;
|
||||||
|
newChanges.push(srdChange);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newChanges.push(change);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldUpdate) {
|
||||||
|
return {
|
||||||
|
_id: effectSource._id,
|
||||||
|
system: { changes: newChanges }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
module/systemRegistration/migration-handlers/2_6_0.mjs
Normal file
20
module/systemRegistration/migration-handlers/2_6_0.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { MigrationHandlerBase } from './base.mjs';
|
||||||
|
|
||||||
|
export class Migration_2_6_0 extends MigrationHandlerBase {
|
||||||
|
version = '2.6.0';
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async updateActorSource(actor) {
|
||||||
|
if (actor.type === 'party' && Object.keys(actor.system.tagTeam.members).length) {
|
||||||
|
return {
|
||||||
|
_id: actor._id,
|
||||||
|
system: {
|
||||||
|
tagTeam: {
|
||||||
|
initiator: null,
|
||||||
|
members: _replace({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
module/systemRegistration/migration-handlers/base.mjs
Normal file
117
module/systemRegistration/migration-handlers/base.mjs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* @import DHItem from "../../documents/item.mjs";
|
||||||
|
* @import DhActor from "../../documents/actor.mjs";
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
|
||||||
|
* The base class of an async migration.
|
||||||
|
* These are generally run between versions for things that require compendiums or must be done in post.
|
||||||
|
* The migrate() functions calls the various updateXSource() functions.
|
||||||
|
* Generally a subclass will override the version and the updateXSource() functions.
|
||||||
|
*/
|
||||||
|
export class MigrationHandlerBase {
|
||||||
|
version = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets change data for an active effect's source, or null if no changes
|
||||||
|
* @param {object} effectSource
|
||||||
|
* @param {DHItem} item
|
||||||
|
* @returns {Promise<object>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async updateActiveEffectSource(effectSource, item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a world actor
|
||||||
|
* @param {DhActor} actor
|
||||||
|
* @returns {Promise<object>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async updateActorSource(actor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async migrate() {
|
||||||
|
// todo: handle more than just migrating effects. Right now this can only migrate effects
|
||||||
|
// NOTE: the preload is hardcoded, we should not hardcode it
|
||||||
|
|
||||||
|
const numActors = game.actors.size;
|
||||||
|
const numItems = game.items.size;
|
||||||
|
const finalUpdateProgress = 5;
|
||||||
|
const DhProgress = game.system.api.applications.ui.DhProgress;
|
||||||
|
const preRunProgress = game.packs.size;
|
||||||
|
|
||||||
|
const progress = DhProgress.createMigrationProgress(
|
||||||
|
preRunProgress + numActors + numItems + finalUpdateProgress
|
||||||
|
);
|
||||||
|
|
||||||
|
// Preload. Avoid hardcoding in the future
|
||||||
|
for (const pack of game.packs) {
|
||||||
|
await pack.getDocuments();
|
||||||
|
progress.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
const batch = [];
|
||||||
|
|
||||||
|
const updateItem = async item => {
|
||||||
|
const itemUpdates = [];
|
||||||
|
for (const effect of item.effects) {
|
||||||
|
const changes = await this.updateActiveEffectSource(effect.toObject(), item);
|
||||||
|
if (changes) itemUpdates.push(changes);
|
||||||
|
}
|
||||||
|
if (itemUpdates.length) {
|
||||||
|
batch.push({
|
||||||
|
action: 'update',
|
||||||
|
documentName: 'ActiveEffect',
|
||||||
|
updates: itemUpdates,
|
||||||
|
parent: item
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateActor = async actor => {
|
||||||
|
const actorUpdate = await this.updateActorSource(actor);
|
||||||
|
if (actorUpdate) {
|
||||||
|
batch.push({
|
||||||
|
action: 'update',
|
||||||
|
documentName: 'Actor',
|
||||||
|
updates: [actorUpdate]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const aeUpdates = [];
|
||||||
|
for (const item of actor.items) {
|
||||||
|
await updateItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const effect of actor.effects) {
|
||||||
|
const changes = await this.updateActiveEffectSource(effect.toObject(), { parent: actor });
|
||||||
|
if (changes) aeUpdates.push(changes);
|
||||||
|
}
|
||||||
|
if (aeUpdates.length) {
|
||||||
|
batch.push({
|
||||||
|
action: 'update',
|
||||||
|
documentName: 'ActiveEffect',
|
||||||
|
updates: aeUpdates,
|
||||||
|
parent: actor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (const actor of game.actors) {
|
||||||
|
await updateActor(actor);
|
||||||
|
|
||||||
|
progress.advance();
|
||||||
|
}
|
||||||
|
for (const item of game.items) {
|
||||||
|
await updateItem(item);
|
||||||
|
progress.advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
await foundry.documents.modifyBatch(batch);
|
||||||
|
progress.advance({ by: finalUpdateProgress });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { defaultRestOptions } from '../config/generalConfig.mjs';
|
import { defaultRestOptions } from '../config/generalConfig.mjs';
|
||||||
import { RefreshType, socketEvent } from './socket.mjs';
|
import { Migration_2_5_2 } from './migration-handlers/2_5_2.mjs';
|
||||||
|
import { Migration_2_6_0 } from './migration-handlers/2_6_0.mjs';
|
||||||
|
|
||||||
export async function runMigrations() {
|
export async function runMigrations() {
|
||||||
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
|
||||||
|
|
@ -153,47 +154,11 @@ export async function runMigrations() {
|
||||||
await pack.configure({ locked: true });
|
await pack.configure({ locked: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Migrate old countdown structure */
|
|
||||||
const countdownSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
|
||||||
const getCountdowns = (data, type) => {
|
|
||||||
return Object.keys(data.countdowns).reduce((acc, key) => {
|
|
||||||
const countdown = data.countdowns[key];
|
|
||||||
acc[key] = {
|
|
||||||
...countdown,
|
|
||||||
type: type,
|
|
||||||
ownership: Object.keys(countdown.ownership.players).reduce((acc, key) => {
|
|
||||||
acc[key] =
|
|
||||||
countdown.ownership.players[key].type === 1 ? 2 : countdown.ownership.players[key].type;
|
|
||||||
return acc;
|
|
||||||
}, {}),
|
|
||||||
progress: {
|
|
||||||
...countdown.progress,
|
|
||||||
type: countdown.progress.type.value
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
};
|
|
||||||
|
|
||||||
await countdownSettings.updateSource({
|
|
||||||
countdowns: {
|
|
||||||
...getCountdowns(countdownSettings.narrative, 'narrative'),
|
|
||||||
...getCountdowns(countdownSettings.encounter, 'encounter')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, countdownSettings);
|
|
||||||
|
|
||||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data: { refreshType: RefreshType.Countdown }
|
|
||||||
});
|
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
|
||||||
|
|
||||||
lastMigrationVersion = '1.2.0';
|
lastMigrationVersion = '1.2.0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) {
|
if (foundry.utils.isNewerVersion('1.2.7', lastMigrationVersion)) {
|
||||||
|
try {
|
||||||
const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll');
|
const tagTeam = game.settings.get(CONFIG.DH.id, 'TagTeamRoll');
|
||||||
const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator);
|
const initatorMissing = tagTeam.initiator && !game.actors.some(actor => actor.id === tagTeam.initiator);
|
||||||
const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => {
|
const missingMembers = Object.keys(tagTeam.members).reduce((acc, id) => {
|
||||||
|
|
@ -208,6 +173,7 @@ export async function runMigrations() {
|
||||||
members: missingMembers
|
members: missingMembers
|
||||||
});
|
});
|
||||||
await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam);
|
await game.settings.set(CONFIG.DH.id, 'TagTeamRoll', tagTeam);
|
||||||
|
} catch { }
|
||||||
|
|
||||||
lastMigrationVersion = '1.2.7';
|
lastMigrationVersion = '1.2.7';
|
||||||
}
|
}
|
||||||
|
|
@ -303,6 +269,8 @@ export async function runMigrations() {
|
||||||
|
|
||||||
/* Migrate existing effects modifying armor, creating new Armor Effects instead */
|
/* Migrate existing effects modifying armor, creating new Armor Effects instead */
|
||||||
const migrateEffects = async entity => {
|
const migrateEffects = async entity => {
|
||||||
|
if (!entity?.effects) return;
|
||||||
|
|
||||||
for (const effect of entity.effects) {
|
for (const effect of entity.effects) {
|
||||||
if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue;
|
if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue;
|
||||||
|
|
||||||
|
|
@ -354,7 +322,20 @@ export async function runMigrations() {
|
||||||
|
|
||||||
lastMigrationVersion = '2.1.0';
|
lastMigrationVersion = '2.1.0';
|
||||||
}
|
}
|
||||||
//#endregion
|
|
||||||
|
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* New Style migrations below this point */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
const migrations = [
|
||||||
|
new Migration_2_5_2(),
|
||||||
|
new Migration_2_6_0()
|
||||||
|
].filter(m => m.version && foundry.utils.isNewerVersion(m.version, lastMigrationVersion));
|
||||||
|
|
||||||
|
for (const handler of migrations) {
|
||||||
|
await handler.migrate();
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, handler.version);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
|
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
|
||||||
import Party from '../applications/sheets/actors/party.mjs';
|
import PartySheet from '../applications/sheets/actors/party.mjs';
|
||||||
|
|
||||||
export function handleSocketEvent({ action = null, data = {} } = {}) {
|
export function handleSocketEvent({ action = null, data = {} } = {}) {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
|
|
@ -16,7 +16,7 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
|
||||||
Hooks.call(socketEvent.Refresh, data);
|
Hooks.call(socketEvent.Refresh, data);
|
||||||
break;
|
break;
|
||||||
case socketEvent.DowntimeTrigger:
|
case socketEvent.DowntimeTrigger:
|
||||||
Party.downtimeMoveQuery(data);
|
PartySheet.downtimeMoveQuery(data);
|
||||||
break;
|
break;
|
||||||
case socketEvent.TagTeamStart:
|
case socketEvent.TagTeamStart:
|
||||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
||||||
|
|
|
||||||
|
|
@ -403,12 +403,6 @@
|
||||||
"_id": "9PsnogEPsp1OOK64",
|
"_id": "9PsnogEPsp1OOK64",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "act"
|
"type": "act"
|
||||||
|
|
|
||||||
|
|
@ -322,31 +322,22 @@
|
||||||
"_id": "ia0NUIOxE3RHb45P",
|
"_id": "ia0NUIOxE3RHb45P",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.evasion",
|
"key": "system.evasion",
|
||||||
"mode": 5,
|
"type": "override",
|
||||||
"value": "@system.evasion / 2",
|
"value": "@system.evasion / 2",
|
||||||
"priority": 10
|
"priority": 10
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"disabled": false,
|
|
||||||
"duration": {
|
|
||||||
"startTime": null,
|
|
||||||
"combat": null,
|
|
||||||
"seconds": null,
|
|
||||||
"rounds": null,
|
|
||||||
"turns": null,
|
|
||||||
"startRound": null,
|
|
||||||
"startTurn": null
|
|
||||||
},
|
},
|
||||||
|
"duration": {
|
||||||
|
"value": null,
|
||||||
|
"units": "seconds",
|
||||||
|
"expiry": null,
|
||||||
|
"expired": false
|
||||||
|
},
|
||||||
|
"disabled": false,
|
||||||
"description": "<p>When the Flickerfly makes an attack, the target’s Evasion is halved against the attack.</p>",
|
"description": "<p>When the Flickerfly makes an attack, the target’s Evasion is halved against the attack.</p>",
|
||||||
"tint": "#ffffff",
|
"tint": "#ffffff",
|
||||||
"statuses": [],
|
"statuses": [],
|
||||||
|
|
|
||||||
|
|
@ -491,12 +491,6 @@
|
||||||
"_id": "KGdf2eqcXkdigg0u",
|
"_id": "KGdf2eqcXkdigg0u",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -325,21 +325,15 @@
|
||||||
"_id": "wGuxOLokMqdxVSOo",
|
"_id": "wGuxOLokMqdxVSOo",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.disadvantageSources",
|
"key": "system.disadvantageSources",
|
||||||
"mode": 2,
|
"type": "add",
|
||||||
"value": "Agility Rolls",
|
"value": "Agility Rolls",
|
||||||
"priority": null
|
"priority": null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
|
||||||
|
|
@ -472,14 +472,8 @@
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"_id": "bq8hTzQoCXmA3xad",
|
"_id": "bq8hTzQoCXmA3xad",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
"changes": []
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"changes": [],
|
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": 0,
|
"startTime": 0,
|
||||||
"combat": null
|
"combat": null
|
||||||
|
|
|
||||||
|
|
@ -270,12 +270,6 @@
|
||||||
"_id": "2iBVUGHtGW3I9VIj",
|
"_id": "2iBVUGHtGW3I9VIj",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
@ -341,23 +335,17 @@
|
||||||
"name": "Out of Nowhere",
|
"name": "Out of Nowhere",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_id": "qT5nh7OcQ6aGdiWS",
|
|
||||||
"img": "icons/magic/perception/shadow-stealth-eyes-purple.webp",
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.advantageSources",
|
"key": "system.advantageSources",
|
||||||
"mode": 2,
|
"type": "add",
|
||||||
"value": "On attacks if they are Hidden.",
|
"value": "On attacks if they are Hidden.",
|
||||||
"priority": null
|
"priority": null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
"_id": "qT5nh7OcQ6aGdiWS",
|
||||||
|
"img": "icons/magic/perception/shadow-stealth-eyes-purple.webp",
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
@ -452,12 +440,6 @@
|
||||||
"_id": "yP4ot8VqS56RnxnE",
|
"_id": "yP4ot8VqS56RnxnE",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": []
|
"changes": []
|
||||||
},
|
},
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
|
|
|
||||||
|
|
@ -680,12 +680,6 @@
|
||||||
"_id": "9NQcCXMhjyBReJRd",
|
"_id": "9NQcCXMhjyBReJRd",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
@ -927,12 +921,6 @@
|
||||||
"_id": "S7kJlhnV8Nexzi8l",
|
"_id": "S7kJlhnV8Nexzi8l",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -363,12 +363,6 @@
|
||||||
"_id": "U50Ccm9emMqAxma6",
|
"_id": "U50Ccm9emMqAxma6",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -371,12 +371,6 @@
|
||||||
"_id": "LmzztuktRkwOCy1a",
|
"_id": "LmzztuktRkwOCy1a",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -447,12 +447,6 @@
|
||||||
"_id": "CjMrSdL6kgD8mKRQ",
|
"_id": "CjMrSdL6kgD8mKRQ",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary"
|
"type": "temporary"
|
||||||
|
|
|
||||||
|
|
@ -232,23 +232,17 @@
|
||||||
"name": "Levitation",
|
"name": "Levitation",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_id": "k6iaQVfMZhrpwYQj",
|
|
||||||
"img": "icons/magic/air/fog-gas-smoke-blue-gray.webp",
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.rules.conditionImmunities.restrained",
|
"key": "system.rules.conditionImmunities.restrained",
|
||||||
"mode": 5,
|
"type": "override",
|
||||||
"value": "1",
|
"value": "1",
|
||||||
"priority": null
|
"priority": null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
"_id": "k6iaQVfMZhrpwYQj",
|
||||||
|
"img": "icons/magic/air/fog-gas-smoke-blue-gray.webp",
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
@ -301,23 +295,17 @@
|
||||||
"name": "Wards",
|
"name": "Wards",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_id": "4rj6PM0AGrrlbxSc",
|
|
||||||
"img": "icons/magic/defensive/barrier-shield-dome-blue-purple.webp",
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.resistance.magical.resistance",
|
"key": "system.resistance.magical.resistance",
|
||||||
"mode": 5,
|
"type": "override",
|
||||||
"value": "1",
|
"value": "1",
|
||||||
"priority": null
|
"priority": null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
"_id": "4rj6PM0AGrrlbxSc",
|
||||||
|
"img": "icons/magic/defensive/barrier-shield-dome-blue-purple.webp",
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
|
||||||
|
|
@ -459,21 +459,15 @@
|
||||||
"_id": "xkDIZk9u2ipDHvOL",
|
"_id": "xkDIZk9u2ipDHvOL",
|
||||||
"img": "icons/creatures/magical/construct-golem-stone-blue.webp",
|
"img": "icons/creatures/magical/construct-golem-stone-blue.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.bonuses.damage.physical.bonus",
|
"key": "system.bonuses.damage.physical.bonus",
|
||||||
"mode": 2,
|
"type": "add",
|
||||||
"value": "10",
|
"value": "10",
|
||||||
"priority": null
|
"priority": null
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
"disabled": true,
|
"disabled": true,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
|
||||||
|
|
@ -315,12 +315,6 @@
|
||||||
"_id": "blcRqns0PHqiuPac",
|
"_id": "blcRqns0PHqiuPac",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -331,12 +331,6 @@
|
||||||
"_id": "YNMhgBZW8ndrCjIp",
|
"_id": "YNMhgBZW8ndrCjIp",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "scene"
|
"type": "scene"
|
||||||
|
|
|
||||||
|
|
@ -415,12 +415,6 @@
|
||||||
"_id": "U9lWz1LgeAiK5L85",
|
"_id": "U9lWz1LgeAiK5L85",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.resistance.magical.resistance",
|
"key": "system.resistance.magical.resistance",
|
||||||
|
|
@ -546,12 +540,6 @@
|
||||||
"_id": "lNH6srSPyEprXZ4o",
|
"_id": "lNH6srSPyEprXZ4o",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -379,12 +379,6 @@
|
||||||
"_id": "LwWxRz7FTMA80VdA",
|
"_id": "LwWxRz7FTMA80VdA",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary"
|
"type": "temporary"
|
||||||
|
|
|
||||||
|
|
@ -410,12 +410,6 @@
|
||||||
"_id": "F3E7fiz01AbF2kr5",
|
"_id": "F3E7fiz01AbF2kr5",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
},
|
|
||||||
"changes": [],
|
"changes": [],
|
||||||
"duration": {
|
"duration": {
|
||||||
"type": "temporary",
|
"type": "temporary",
|
||||||
|
|
|
||||||
|
|
@ -270,23 +270,17 @@
|
||||||
"name": "Numbers Must Go Up",
|
"name": "Numbers Must Go Up",
|
||||||
"type": "base",
|
"type": "base",
|
||||||
"system": {
|
"system": {
|
||||||
"rangeDependence": {
|
|
||||||
"enabled": false,
|
|
||||||
"type": "withinRange",
|
|
||||||
"target": "hostile",
|
|
||||||
"range": "melee"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"_id": "iLeiOlUtWvYsgbDQ",
|
|
||||||
"img": "icons/magic/control/fear-fright-monster-grin-red-orange.webp",
|
|
||||||
"changes": [
|
"changes": [
|
||||||
{
|
{
|
||||||
"key": "system.bonuses.roll.attack.bonus",
|
"key": "system.bonuses.roll.attack.bonus",
|
||||||
"mode": 2,
|
"type": "add",
|
||||||
"value": "ITEM.@system.resource.value",
|
"value": "ITEM.@system.resource.value",
|
||||||
"priority": 21
|
"priority": 21
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
|
},
|
||||||
|
"_id": "iLeiOlUtWvYsgbDQ",
|
||||||
|
"img": "icons/magic/control/fear-fright-monster-grin-red-orange.webp",
|
||||||
"disabled": false,
|
"disabled": false,
|
||||||
"duration": {
|
"duration": {
|
||||||
"startTime": null,
|
"startTime": null,
|
||||||
|
|
|
||||||
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