mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
Compare commits
105 commits
b4c2034789
...
0b343c9f52
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b343c9f52 | ||
|
|
e6973fabd0 | ||
|
|
4e18ed8270 | ||
|
|
e7cf6594b6 | ||
|
|
bbe8fb953e | ||
|
|
6cebccd958 | ||
|
|
248f7b41e7 | ||
|
|
c6bdc846ab | ||
|
|
6deadea437 | ||
|
|
9564edb244 | ||
|
|
bca7e0d3c9 | ||
|
|
3b7b6258a1 | ||
|
|
e8c541c002 | ||
|
|
f6bd1430e3 | ||
|
|
c070c6cc2d | ||
|
|
d0e55aeb8d | ||
|
|
f11b018bd7 | ||
|
|
c83fe25a47 | ||
|
|
3405b53900 | ||
|
|
5f001a9f83 | ||
|
|
87dfebec2f | ||
|
|
ab7ea03d84 | ||
|
|
09aafd0999 | ||
|
|
52b32a4d12 | ||
|
|
fa21baf8bf | ||
|
|
50a307b271 | ||
|
|
c63ba3b41d | ||
|
|
2104549617 | ||
|
|
92b31b71a7 | ||
|
|
f8b003b304 | ||
|
|
0806c2d1ac | ||
|
|
f184db1f93 | ||
|
|
7e2b144bf4 | ||
|
|
1b7893324a | ||
|
|
0f5f866b22 | ||
|
|
a168d8de65 | ||
|
|
51eadc499f | ||
|
|
f0531d3587 | ||
|
|
790a5b4938 | ||
|
|
8178fa5738 | ||
|
|
7926c614e3 | ||
|
|
16f6fa98a6 | ||
|
|
659f73116a | ||
|
|
e8dd38fbfa | ||
|
|
99d0eab5bd | ||
|
|
f786ee5f06 | ||
|
|
b8e08fccd1 | ||
|
|
fe80b4d0f8 | ||
|
|
148c9c019a | ||
|
|
9cfa206adc | ||
|
|
0508bf4188 | ||
|
|
605a23ab58 | ||
|
|
7d1e70f66f | ||
|
|
474cf28a53 | ||
|
|
5f6d08d8c2 | ||
|
|
a8b15c8252 | ||
|
|
27fe83d906 | ||
|
|
0936b46926 | ||
|
|
05dec9fcea | ||
|
|
e74ce7726a | ||
|
|
9b4249b100 | ||
|
|
f60792f714 | ||
|
|
d5b8431f88 | ||
|
|
315f1ef8e0 | ||
|
|
6cb635901f | ||
|
|
a8c120be8e | ||
|
|
7a50d77952 | ||
|
|
64caff6fb2 | ||
|
|
46a9aea029 | ||
|
|
360b903437 | ||
|
|
f4dd9dc5c1 | ||
|
|
00e9436fe0 | ||
|
|
7f7536ee06 | ||
|
|
8eae1c0763 | ||
|
|
2b1535333a | ||
|
|
09141053c9 | ||
|
|
5356f10b2a | ||
|
|
91d916a28d | ||
|
|
b307d65d18 | ||
|
|
ccdd413933 | ||
|
|
f680ade1da | ||
|
|
d6b1c7a36c | ||
|
|
28976bb4b8 | ||
|
|
14ac8977af | ||
|
|
2a622a7363 | ||
|
|
6ae00e15bd | ||
|
|
c846c5bc85 | ||
|
|
451bef4c92 | ||
|
|
c3cb9121af | ||
|
|
6d8d773a26 | ||
|
|
b57e98071f | ||
|
|
2171c1b433 | ||
|
|
1fbce2507a | ||
|
|
e3f244d8d7 | ||
|
|
9fa4627b19 | ||
|
|
cb10b18e06 | ||
|
|
5163bf9788 | ||
|
|
82d39a3d70 | ||
|
|
01a91724ed | ||
|
|
8f917c3640 | ||
|
|
e57e7327d6 | ||
|
|
165068a9ee | ||
|
|
f41f0b20b7 | ||
|
|
f29198e81f | ||
|
|
630ba5ab7d |
1366 changed files with 11350 additions and 24375 deletions
209
daggerheart.mjs
209
daggerheart.mjs
|
|
@ -17,7 +17,6 @@ import {
|
||||||
socketRegistration
|
socketRegistration
|
||||||
} from './module/systemRegistration/_module.mjs';
|
} from './module/systemRegistration/_module.mjs';
|
||||||
import { placeables } from './module/canvas/_module.mjs';
|
import { placeables } from './module/canvas/_module.mjs';
|
||||||
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
|
|
||||||
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
import './node_modules/@yaireo/tagify/dist/tagify.css';
|
||||||
import TemplateManager from './module/documents/templateManager.mjs';
|
import TemplateManager from './module/documents/templateManager.mjs';
|
||||||
|
|
||||||
|
|
@ -43,6 +42,7 @@ CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||||
|
|
||||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||||
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||||
|
CONFIG.Combatant.documentClass = documents.DHCombatant;
|
||||||
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
|
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
|
||||||
|
|
||||||
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
||||||
|
|
@ -53,6 +53,8 @@ CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
|
CONFIG.Scene.documentClass = documents.DhScene;
|
||||||
|
|
||||||
CONFIG.Token.documentClass = documents.DhToken;
|
CONFIG.Token.documentClass = documents.DhToken;
|
||||||
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||||
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||||
|
|
@ -61,8 +63,10 @@ CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||||
|
|
||||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||||
|
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
|
||||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||||
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
|
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
|
||||||
|
CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
|
||||||
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
|
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
|
||||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||||
|
|
@ -86,34 +90,94 @@ Hooks.once('init', () => {
|
||||||
makeDefault: true
|
makeDefault: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sheetLabel = typePath => () =>
|
||||||
|
game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
|
||||||
|
type: game.i18n.localize(typePath)
|
||||||
|
});
|
||||||
|
|
||||||
const { Items, Actors } = foundry.documents.collections;
|
const { Items, Actors } = foundry.documents.collections;
|
||||||
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
|
Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, { types: ['ancestry'], makeDefault: true });
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, {
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, { types: ['community'], makeDefault: true });
|
types: ['ancestry'],
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, { types: ['class'], makeDefault: true });
|
makeDefault: true,
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, { types: ['subclass'], makeDefault: true });
|
label: sheetLabel('TYPES.Item.ancestry')
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, { types: ['feature'], makeDefault: true });
|
});
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, { types: ['domainCard'], makeDefault: true });
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, {
|
||||||
|
types: ['community'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.community')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, {
|
||||||
|
types: ['class'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.class')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, {
|
||||||
|
types: ['subclass'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.subclass')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, {
|
||||||
|
types: ['feature'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.feature')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, {
|
||||||
|
types: ['domainCard'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.domainCard')
|
||||||
|
});
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
|
||||||
types: ['loot'],
|
types: ['loot'],
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.loot')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, {
|
||||||
|
types: ['consumable'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.consumable')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, {
|
||||||
|
types: ['weapon'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.weapon')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, {
|
||||||
|
types: ['armor'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.armor')
|
||||||
|
});
|
||||||
|
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, {
|
||||||
|
types: ['beastform'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Item.beastform')
|
||||||
});
|
});
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, { types: ['consumable'], makeDefault: true });
|
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, { types: ['weapon'], makeDefault: true });
|
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, { types: ['armor'], makeDefault: true });
|
|
||||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, { types: ['beastform'], makeDefault: true });
|
|
||||||
|
|
||||||
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
|
Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { types: ['character'], makeDefault: true });
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, {
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, { types: ['companion'], makeDefault: true });
|
types: ['character'],
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, { types: ['adversary'], makeDefault: true });
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.character')
|
||||||
|
});
|
||||||
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, {
|
||||||
|
types: ['companion'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.companion')
|
||||||
|
});
|
||||||
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, {
|
||||||
|
types: ['adversary'],
|
||||||
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.adversary')
|
||||||
|
});
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
|
||||||
types: ['environment'],
|
types: ['environment'],
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.environment')
|
||||||
});
|
});
|
||||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
|
||||||
types: ['party'],
|
types: ['party'],
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('TYPES.Actor.party')
|
||||||
});
|
});
|
||||||
|
|
||||||
DocumentSheetConfig.unregisterSheet(
|
DocumentSheetConfig.unregisterSheet(
|
||||||
|
|
@ -126,7 +190,8 @@ Hooks.once('init', () => {
|
||||||
SYSTEM.id,
|
SYSTEM.id,
|
||||||
applications.sheetConfigs.ActiveEffectConfig,
|
applications.sheetConfigs.ActiveEffectConfig,
|
||||||
{
|
{
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('DOCUMENT.ActiveEffect')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -135,9 +200,10 @@ Hooks.once('init', () => {
|
||||||
// Make Compendium Dialog resizable
|
// Make Compendium Dialog resizable
|
||||||
foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;
|
foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;
|
||||||
|
|
||||||
|
DocumentSheetConfig.unregisterSheet(foundry.documents.Scene, 'core', foundry.applications.sheets.SceneConfig);
|
||||||
DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, {
|
DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, {
|
||||||
makeDefault: true,
|
makeDefault: true,
|
||||||
label: 'Daggerheart'
|
label: sheetLabel('DOCUMENT.Scene')
|
||||||
});
|
});
|
||||||
|
|
||||||
settingsRegistration.registerDHSettings();
|
settingsRegistration.registerDHSettings();
|
||||||
|
|
@ -167,11 +233,13 @@ Hooks.on('ready', async () => {
|
||||||
ui.countdowns.render({ force: true });
|
ui.countdowns.render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.effectsDisplay = new CONFIG.ui.effectsDisplay();
|
||||||
|
ui.effectsDisplay.render({ force: true });
|
||||||
|
|
||||||
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
||||||
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
||||||
|
|
||||||
socketRegistration.registerSocketHooks();
|
socketRegistration.registerSocketHooks();
|
||||||
registerRollDiceHooks();
|
|
||||||
socketRegistration.registerUserQueries();
|
socketRegistration.registerUserQueries();
|
||||||
|
|
||||||
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
|
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
|
||||||
|
|
@ -187,9 +255,9 @@ Hooks.on('ready', async () => {
|
||||||
|
|
||||||
Hooks.once('dicesoniceready', () => {});
|
Hooks.once('dicesoniceready', () => {});
|
||||||
|
|
||||||
Hooks.on('renderChatMessageHTML', (_, element, message) => {
|
Hooks.on('renderChatMessageHTML', (document, element) => {
|
||||||
enricherRenderSetup(element);
|
enricherRenderSetup(element);
|
||||||
const cssClass = message.message.flags?.daggerheart?.cssClass;
|
const cssClass = document.flags?.daggerheart?.cssClass;
|
||||||
if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
|
if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -242,51 +310,70 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('moveToken', async (movedToken, data) => {
|
const updateActorsRangeDependentEffects = async token => {
|
||||||
|
const rangeMeasurement = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||||
|
).rangeMeasurement;
|
||||||
|
|
||||||
|
for (let effect of token.actor?.allApplicableEffects() ?? []) {
|
||||||
|
if (!effect.system.rangeDependence?.enabled) continue;
|
||||||
|
const { target, range, type } = effect.system.rangeDependence;
|
||||||
|
|
||||||
|
// If there are no targets, assume false. Otherwise, start with the effect enabled.
|
||||||
|
let enabledEffect = game.user.targets.size !== 0;
|
||||||
|
// Expect all targets to meet the rangeDependence requirements
|
||||||
|
for (let userTarget of game.user.targets) {
|
||||||
|
const disposition = userTarget.document.disposition;
|
||||||
|
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) {
|
||||||
|
enabledEffect = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get required distance and special case 5 feet to test adjacency
|
||||||
|
const required = rangeMeasurement[range];
|
||||||
|
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
||||||
|
const inRange =
|
||||||
|
required === 5
|
||||||
|
? userTarget.isAdjacentWith(token.object)
|
||||||
|
: userTarget.distanceTo(token.object) <= required;
|
||||||
|
if (reverse ? inRange : !inRange) {
|
||||||
|
enabledEffect = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await effect.update({ disabled: !enabledEffect });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAllRangeDependentEffects = async () => {
|
||||||
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||||
if (!effectsAutomation.rangeDependent) return;
|
if (!effectsAutomation.rangeDependent) return;
|
||||||
|
|
||||||
const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence?.enabled);
|
const tokens = canvas.scene.tokens;
|
||||||
|
if (game.user.character) {
|
||||||
const updateEffects = async (disposition, token, effects, effectUpdates) => {
|
// The character updates their character's token. There can be only one token.
|
||||||
const rangeMeasurement = game.settings.get(
|
const characterToken = tokens.find(x => x.actor === game.user.character);
|
||||||
CONFIG.DH.id,
|
updateActorsRangeDependentEffects(characterToken);
|
||||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
} else if (game.user.isActiveGM) {
|
||||||
).rangeMeasurement;
|
// The GM is responsible for all other tokens.
|
||||||
|
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
|
||||||
for (let effect of effects.filter(x => x.system.rangeDependence?.enabled)) {
|
for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
|
||||||
const { target, range, type } = effect.system.rangeDependence;
|
updateActorsRangeDependentEffects(token);
|
||||||
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const distanceBetween = canvas.grid.measurePath([
|
|
||||||
{ ...movedToken.toObject(), x: data.destination.x, y: data.destination.y },
|
|
||||||
token
|
|
||||||
]).distance;
|
|
||||||
const distance = rangeMeasurement[range];
|
|
||||||
|
|
||||||
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
|
||||||
const newDisabled = reverse ? distanceBetween <= distance : distanceBetween > distance;
|
|
||||||
const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
|
|
||||||
effectUpdates[effect.uuid] = {
|
|
||||||
disabled: oldDisabled || newDisabled,
|
|
||||||
value: effect
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const effectUpdates = {};
|
|
||||||
for (let token of game.scenes.find(x => x.active).tokens) {
|
|
||||||
if (token.id !== movedToken.id) {
|
|
||||||
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (let key in effectUpdates) {
|
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);
|
||||||
const effect = effectUpdates[key];
|
|
||||||
await effect.value.update({ disabled: effect.disabled });
|
Hooks.on('targetToken', () => {
|
||||||
|
debouncedRangeEffectCall();
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on('refreshToken', (_, options) => {
|
||||||
|
if (options.refreshPosition) {
|
||||||
|
debouncedRangeEffectCall();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
118
lang/en.json
118
lang/en.json
|
|
@ -36,6 +36,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"DAGGERHEART": {
|
"DAGGERHEART": {
|
||||||
|
"CharacterSheet": "Character Sheet",
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
"attack": {
|
"attack": {
|
||||||
|
|
@ -225,6 +226,7 @@
|
||||||
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
|
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
|
||||||
},
|
},
|
||||||
"viewLevelups": "View Levelups",
|
"viewLevelups": "View Levelups",
|
||||||
|
"viewParty": "View Party",
|
||||||
"InvalidOldCharacterImportTitle": "Old Character Import",
|
"InvalidOldCharacterImportTitle": "Old Character Import",
|
||||||
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
|
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
|
||||||
"cancelBeastform": "Cancel Beastform"
|
"cancelBeastform": "Cancel Beastform"
|
||||||
|
|
@ -325,6 +327,7 @@
|
||||||
"equip": "Equip",
|
"equip": "Equip",
|
||||||
"sendToChat": "Send To Chat",
|
"sendToChat": "Send To Chat",
|
||||||
"toLoadout": "Send to Loadout",
|
"toLoadout": "Send to Loadout",
|
||||||
|
"recall": "Recall",
|
||||||
"toVault": "Send to Vault",
|
"toVault": "Send to Vault",
|
||||||
"unequip": "Unequip",
|
"unequip": "Unequip",
|
||||||
"useItem": "Use Item"
|
"useItem": "Use Item"
|
||||||
|
|
@ -386,7 +389,8 @@
|
||||||
"hideNewCountdowns": "Hide New Countdowns"
|
"hideNewCountdowns": "Hide New Countdowns"
|
||||||
},
|
},
|
||||||
"DaggerheartMenu": {
|
"DaggerheartMenu": {
|
||||||
"title": "GM Tools"
|
"title": "GM Tools",
|
||||||
|
"refreshFeatures": "Refresh Features"
|
||||||
},
|
},
|
||||||
"DeleteConfirmation": {
|
"DeleteConfirmation": {
|
||||||
"title": "Delete {type} - {name}",
|
"title": "Delete {type} - {name}",
|
||||||
|
|
@ -504,6 +508,7 @@
|
||||||
},
|
},
|
||||||
"navigateLevel": "To Level {level}",
|
"navigateLevel": "To Level {level}",
|
||||||
"navigateToLevelup": "Return To Levelup",
|
"navigateToLevelup": "Return To Levelup",
|
||||||
|
"finishLevelup": "Finish Levelup",
|
||||||
"navigateToSummary": "To Summary",
|
"navigateToSummary": "To Summary",
|
||||||
"options": {
|
"options": {
|
||||||
"trait": "Gain a +1 bonus to two unmarked character traits and mark them.",
|
"trait": "Gain a +1 bonus to two unmarked character traits and mark them.",
|
||||||
|
|
@ -583,6 +588,7 @@
|
||||||
},
|
},
|
||||||
"OwnershipSelection": {
|
"OwnershipSelection": {
|
||||||
"title": "Ownership Selection - {name}",
|
"title": "Ownership Selection - {name}",
|
||||||
|
"noPlayers": "No players to assign ownership to",
|
||||||
"default": "Default Ownership"
|
"default": "Default Ownership"
|
||||||
},
|
},
|
||||||
"ReactionRoll": {
|
"ReactionRoll": {
|
||||||
|
|
@ -609,6 +615,9 @@
|
||||||
"insufficientHope": "The initiating character doesn't have enough hope",
|
"insufficientHope": "The initiating character doesn't have enough hope",
|
||||||
"createTagTeam": "Create TagTeam Roll",
|
"createTagTeam": "Create TagTeam Roll",
|
||||||
"chatMessageRollTitle": "Roll"
|
"chatMessageRollTitle": "Roll"
|
||||||
|
},
|
||||||
|
"TokenConfig": {
|
||||||
|
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CLASS": {
|
"CLASS": {
|
||||||
|
|
@ -618,11 +627,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"ActionType": {
|
|
||||||
"passive": "Passive",
|
|
||||||
"action": "Action",
|
|
||||||
"reaction": "Reaction"
|
|
||||||
},
|
|
||||||
"AdversaryTrait": {
|
"AdversaryTrait": {
|
||||||
"relentless": {
|
"relentless": {
|
||||||
"name": "Relentless",
|
"name": "Relentless",
|
||||||
|
|
@ -682,6 +686,14 @@
|
||||||
"description": "Enemies that enhance their allies and/or disrupt their opponents."
|
"description": "Enemies that enhance their allies and/or disrupt their opponents."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AdversaryTypeCost": {
|
||||||
|
"minion": "for each group of Minions equal to the size of the party.",
|
||||||
|
"support": "for each Social or Support adversary.",
|
||||||
|
"standard": "for each Horde, Ranged, Skulk, or Standard adversary.",
|
||||||
|
"leader": "for each Leader adversary.",
|
||||||
|
"bruiser": "for each Bruiser adversary.",
|
||||||
|
"solo": "for each Solo adversary."
|
||||||
|
},
|
||||||
"ArmorFeature": {
|
"ArmorFeature": {
|
||||||
"burning": {
|
"burning": {
|
||||||
"name": "Burning",
|
"name": "Burning",
|
||||||
|
|
@ -905,6 +917,30 @@
|
||||||
"evolved": "Evolved",
|
"evolved": "Evolved",
|
||||||
"hybrid": "Hybrid"
|
"hybrid": "Hybrid"
|
||||||
},
|
},
|
||||||
|
"BPModifiers": {
|
||||||
|
"increaseDamage": {
|
||||||
|
"description": "if you add +1d4 (or a static +2) to all adversaries' damage rolls (to increase the challenge without lengthening the battle)",
|
||||||
|
"effect": {
|
||||||
|
"name": "Increase Damage",
|
||||||
|
"description": "Add 1d4 to damage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lessDifficult": {
|
||||||
|
"description": "if the fight should be less difficult or shorter."
|
||||||
|
},
|
||||||
|
"lowerTier": {
|
||||||
|
"description": "if you choose an adversary from a lower tier."
|
||||||
|
},
|
||||||
|
"manySolos": {
|
||||||
|
"description": "if you're using 2 or more Solo adversaries."
|
||||||
|
},
|
||||||
|
"moreDangerous": {
|
||||||
|
"description": "if the fight should be more dangerous or last longer"
|
||||||
|
},
|
||||||
|
"noToughies": {
|
||||||
|
"description": "if you don't include any Bruisers, Hordes, Leaders, or Solos"
|
||||||
|
}
|
||||||
|
},
|
||||||
"Burden": {
|
"Burden": {
|
||||||
"oneHanded": "One-Handed",
|
"oneHanded": "One-Handed",
|
||||||
"twoHanded": "Two-Handed"
|
"twoHanded": "Two-Handed"
|
||||||
|
|
@ -999,6 +1035,12 @@
|
||||||
"description": ""
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"FeatureForm": {
|
||||||
|
"label": "Feature Form",
|
||||||
|
"passive": "Passive",
|
||||||
|
"action": "Action",
|
||||||
|
"reaction": "Reaction"
|
||||||
|
},
|
||||||
"Gold": {
|
"Gold": {
|
||||||
"title": "Gold",
|
"title": "Gold",
|
||||||
"coins": "Coins",
|
"coins": "Coins",
|
||||||
|
|
@ -1112,6 +1154,14 @@
|
||||||
"rect": "Rectangle",
|
"rect": "Rectangle",
|
||||||
"ray": "Ray"
|
"ray": "Ray"
|
||||||
},
|
},
|
||||||
|
"TokenSize": {
|
||||||
|
"tiny": "Tiny",
|
||||||
|
"small": "Small",
|
||||||
|
"medium": "Medium",
|
||||||
|
"large": "Large",
|
||||||
|
"huge": "Huge",
|
||||||
|
"gargantuan": "Gargantuan"
|
||||||
|
},
|
||||||
"Traits": {
|
"Traits": {
|
||||||
"agility": {
|
"agility": {
|
||||||
"name": "Agility",
|
"name": "Agility",
|
||||||
|
|
@ -1752,7 +1802,9 @@
|
||||||
"label": "Long Rest: Bonus Long Rest Moves",
|
"label": "Long Rest: Bonus Long Rest Moves",
|
||||||
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
|
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"target": "Target",
|
||||||
|
"targetSelf": "Self"
|
||||||
},
|
},
|
||||||
"maxLoadout": {
|
"maxLoadout": {
|
||||||
"label": "Max Loadout Cards Bonus"
|
"label": "Max Loadout Cards Bonus"
|
||||||
|
|
@ -1767,6 +1819,7 @@
|
||||||
"plural": "Costs"
|
"plural": "Costs"
|
||||||
},
|
},
|
||||||
"Damage": {
|
"Damage": {
|
||||||
|
"massive": "Massive",
|
||||||
"severe": "Severe",
|
"severe": "Severe",
|
||||||
"major": "Major",
|
"major": "Major",
|
||||||
"minor": "Minor",
|
"minor": "Minor",
|
||||||
|
|
@ -2032,6 +2085,7 @@
|
||||||
"basics": "Basics",
|
"basics": "Basics",
|
||||||
"bonus": "Bonus",
|
"bonus": "Bonus",
|
||||||
"burden": "Burden",
|
"burden": "Burden",
|
||||||
|
"condition": "Condition",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"criticalSuccess": "Critical Success",
|
"criticalSuccess": "Critical Success",
|
||||||
"criticalShort": "Critical",
|
"criticalShort": "Critical",
|
||||||
|
|
@ -2058,6 +2112,7 @@
|
||||||
"fear": "Fear",
|
"fear": "Fear",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"formula": "Formula",
|
"formula": "Formula",
|
||||||
|
"general": "General",
|
||||||
"gm": "GM",
|
"gm": "GM",
|
||||||
"healing": "Healing",
|
"healing": "Healing",
|
||||||
"healingRoll": "Healing Roll",
|
"healingRoll": "Healing Roll",
|
||||||
|
|
@ -2072,6 +2127,7 @@
|
||||||
},
|
},
|
||||||
"hope": "Hope",
|
"hope": "Hope",
|
||||||
"hordeHp": "Horde HP",
|
"hordeHp": "Horde HP",
|
||||||
|
"icon": "Icon",
|
||||||
"identify": "Identity",
|
"identify": "Identity",
|
||||||
"imagePath": "Image Path",
|
"imagePath": "Image Path",
|
||||||
"inactiveEffects": "Inactive Effects",
|
"inactiveEffects": "Inactive Effects",
|
||||||
|
|
@ -2093,6 +2149,7 @@
|
||||||
"missingDragDropThing": "Drop {thing} here",
|
"missingDragDropThing": "Drop {thing} here",
|
||||||
"multiclass": "Multiclass",
|
"multiclass": "Multiclass",
|
||||||
"newCategory": "New Category",
|
"newCategory": "New Category",
|
||||||
|
"newThing": "New {thing}",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"noTarget": "No current target",
|
"noTarget": "No current target",
|
||||||
"partner": "Partner",
|
"partner": "Partner",
|
||||||
|
|
@ -2107,6 +2164,7 @@
|
||||||
"recovery": "Recovery",
|
"recovery": "Recovery",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"reroll": "Reroll",
|
"reroll": "Reroll",
|
||||||
|
"rerolled": "Rerolled",
|
||||||
"rerollThing": "Reroll {thing}",
|
"rerollThing": "Reroll {thing}",
|
||||||
"resource": "Resource",
|
"resource": "Resource",
|
||||||
"roll": "Roll",
|
"roll": "Roll",
|
||||||
|
|
@ -2127,10 +2185,12 @@
|
||||||
"plural": "Targets"
|
"plural": "Targets"
|
||||||
},
|
},
|
||||||
"title": "Title",
|
"title": "Title",
|
||||||
|
"tokenSize": "Token Size",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"traitModifier": "Trait Modifier",
|
"traitModifier": "Trait Modifier",
|
||||||
"true": "True",
|
"true": "True",
|
||||||
"type": "Type",
|
"type": "Type",
|
||||||
|
"typeSheet": "System {type} Sheet",
|
||||||
"unarmed": "Unarmed",
|
"unarmed": "Unarmed",
|
||||||
"unarmedAttack": "Unarmed Attack",
|
"unarmedAttack": "Unarmed Attack",
|
||||||
"unarmored": "Unarmored",
|
"unarmored": "Unarmored",
|
||||||
|
|
@ -2183,6 +2243,7 @@
|
||||||
"tokenRingImg": { "label": "Subject Texture" },
|
"tokenRingImg": { "label": "Subject Texture" },
|
||||||
"tokenSize": {
|
"tokenSize": {
|
||||||
"placeholder": "Using character dimensions",
|
"placeholder": "Using character dimensions",
|
||||||
|
"disabledPlaceholder": "Set by character size",
|
||||||
"height": { "label": "Height" },
|
"height": { "label": "Height" },
|
||||||
"width": { "label": "Width" }
|
"width": { "label": "Width" }
|
||||||
},
|
},
|
||||||
|
|
@ -2206,7 +2267,11 @@
|
||||||
"evolvedDrag": "Drag a form here to evolve it.",
|
"evolvedDrag": "Drag a form here to evolve it.",
|
||||||
"hybridize": "Hybridize",
|
"hybridize": "Hybridize",
|
||||||
"hybridizeFeatureTitle": "Hybrid Features",
|
"hybridizeFeatureTitle": "Hybrid Features",
|
||||||
"hybridizeDrag": "Drag a form here to hybridize it."
|
"hybridizeDrag": "Drag a form here to hybridize it.",
|
||||||
|
"mainTrait": "Main Trait",
|
||||||
|
"traitBonus": "Trait Bonus",
|
||||||
|
"evolvedTokenHint": "An evolved beastform's token is based on that of the form you evolve",
|
||||||
|
"evolvedImagePlaceholder": "The image for the form selected for evolution will be used"
|
||||||
},
|
},
|
||||||
"Class": {
|
"Class": {
|
||||||
"hopeFeatures": "Hope Features",
|
"hopeFeatures": "Hope Features",
|
||||||
|
|
@ -2382,8 +2447,8 @@
|
||||||
"newDowntimeMove": "Downtime Move",
|
"newDowntimeMove": "Downtime Move",
|
||||||
"downtimeMove": "Downtime Move",
|
"downtimeMove": "Downtime Move",
|
||||||
"armorFeature": "Armor Feature",
|
"armorFeature": "Armor Feature",
|
||||||
"weaponFeature": "Weapon Feaure",
|
"weaponFeature": "Weapon Feature",
|
||||||
"newFeature": "New ItemFeature",
|
"newFeature": "New Item Feature",
|
||||||
"downtimeMoves": "Downtime Moves",
|
"downtimeMoves": "Downtime Moves",
|
||||||
"itemFeatures": "Item Features",
|
"itemFeatures": "Item Features",
|
||||||
"nrChoices": "# Moves Per Rest",
|
"nrChoices": "# Moves Per Rest",
|
||||||
|
|
@ -2400,9 +2465,12 @@
|
||||||
},
|
},
|
||||||
"currency": {
|
"currency": {
|
||||||
"title": "Currency Overrides",
|
"title": "Currency Overrides",
|
||||||
|
"changeIcon": "Change Currency Icon",
|
||||||
"currencyName": "Currency Name",
|
"currencyName": "Currency Name",
|
||||||
"coinName": "Coin Name",
|
"coinName": "Coin Name",
|
||||||
"handfulName": "Handful Name",
|
"handfulName": "Handful Name",
|
||||||
|
"iconName": "Icon Name",
|
||||||
|
"iconNameHint": "Icons are from fontawesome",
|
||||||
"bagName": "Bag Name",
|
"bagName": "Bag Name",
|
||||||
"chestName": "Chest Name"
|
"chestName": "Chest Name"
|
||||||
},
|
},
|
||||||
|
|
@ -2464,6 +2532,11 @@
|
||||||
"hint": "Apply variant rules from the Daggerheart system",
|
"hint": "Apply variant rules from the Daggerheart system",
|
||||||
"name": "Variant Rules",
|
"name": "Variant Rules",
|
||||||
"actionTokens": "Action Tokens"
|
"actionTokens": "Action Tokens"
|
||||||
|
},
|
||||||
|
"SpotlightRequestQueue": {
|
||||||
|
"name": "Spotlight Request Queue",
|
||||||
|
"label": "Spotlight Request Queue",
|
||||||
|
"hint": "Adds more structure to spotlight requests by ordering them from oldest to newest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Resources": {
|
"Resources": {
|
||||||
|
|
@ -2477,6 +2550,10 @@
|
||||||
"actionTokens": {
|
"actionTokens": {
|
||||||
"enabled": { "label": "Enabled" },
|
"enabled": { "label": "Enabled" },
|
||||||
"tokens": { "label": "Tokens" }
|
"tokens": { "label": "Tokens" }
|
||||||
|
},
|
||||||
|
"massiveDamage": {
|
||||||
|
"title": "Massive Damage",
|
||||||
|
"enabled": { "label": "Enabled" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2549,7 +2626,8 @@
|
||||||
"selectMember": "Select a Member",
|
"selectMember": "Select a Member",
|
||||||
"rerollTitle": "Reroll Group Roll",
|
"rerollTitle": "Reroll Group Roll",
|
||||||
"rerollContent": "Are you sure you want to reroll your {trait} check?",
|
"rerollContent": "Are you sure you want to reroll your {trait} check?",
|
||||||
"rerollTooltip": "Reroll"
|
"rerollTooltip": "Reroll",
|
||||||
|
"wholePartySelected": "The whole party is selected"
|
||||||
},
|
},
|
||||||
"healingRoll": {
|
"healingRoll": {
|
||||||
"title": "Heal - {damage}",
|
"title": "Heal - {damage}",
|
||||||
|
|
@ -2585,6 +2663,10 @@
|
||||||
"decreasingLoop": "Decreasing Looping",
|
"decreasingLoop": "Decreasing Looping",
|
||||||
"increasingLoop": "Increasing Looping"
|
"increasingLoop": "Increasing Looping"
|
||||||
},
|
},
|
||||||
|
"EffectsDisplay": {
|
||||||
|
"removeThing": "[Right Click] Remove {thing}",
|
||||||
|
"appliedBy": "Applied By: {by}"
|
||||||
|
},
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
"title": "Daggerheart Compendium Browser",
|
"title": "Daggerheart Compendium Browser",
|
||||||
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
"hint": "Select a Folder in sidebar to start browsing through the compendium",
|
||||||
|
|
@ -2699,9 +2781,17 @@
|
||||||
"gmRequired": "This action requires an online GM",
|
"gmRequired": "This action requires an online GM",
|
||||||
"gmOnly": "This can only be accessed by the GM",
|
"gmOnly": "This can only be accessed by the GM",
|
||||||
"noActorOwnership": "You do not have permissions for this character",
|
"noActorOwnership": "You do not have permissions for this character",
|
||||||
"documentIsMissing": "The {documentType} is missing from the world."
|
"documentIsMissing": "The {documentType} is missing from the world.",
|
||||||
|
"tokenActorMissing": "{name} is missing an Actor",
|
||||||
|
"tokenActorsMissing": "[{names}] missing Actors"
|
||||||
},
|
},
|
||||||
"Sidebar": {
|
"Sidebar": {
|
||||||
|
"actorDirectory": {
|
||||||
|
"tier": "Tier {tier} {type}",
|
||||||
|
"character": "Level {level} Character",
|
||||||
|
"companion": "Level {level} - {partner}",
|
||||||
|
"companionNoPartner": "No Partner"
|
||||||
|
},
|
||||||
"daggerheartMenu": {
|
"daggerheartMenu": {
|
||||||
"title": "Daggerheart Menu",
|
"title": "Daggerheart Menu",
|
||||||
"startSession": "Start Session",
|
"startSession": "Start Session",
|
||||||
|
|
@ -2736,7 +2826,9 @@
|
||||||
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
|
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
|
||||||
"configureAttribution": "Configure Attribution",
|
"configureAttribution": "Configure Attribution",
|
||||||
"deleteItem": "Delete Item",
|
"deleteItem": "Delete Item",
|
||||||
"immune": "Immune"
|
"immune": "Immune",
|
||||||
|
"middleClick": "[Middle Click] Keep tooltip view",
|
||||||
|
"tokenSize": "The token size used on the canvas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -278,19 +278,26 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
||||||
'close',
|
'close',
|
||||||
async () => {
|
async () => {
|
||||||
const selected = app.selected.toObject();
|
const selected = app.selected.toObject();
|
||||||
|
const evolved = app.evolved.form ? app.evolved.form.toObject() : null;
|
||||||
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(
|
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(
|
||||||
app.configData.data.parent,
|
app.configData.data.parent,
|
||||||
app.selected
|
evolved ?? app.selected
|
||||||
);
|
);
|
||||||
if (data) {
|
if (data) {
|
||||||
if (!data.selectedImage) selected = null;
|
if (!data.selectedImage) selected = null;
|
||||||
else {
|
else {
|
||||||
if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage;
|
const imageSource = evolved ?? selected;
|
||||||
else selected.system.tokenImg = data.selectedImage;
|
if (imageSource.usesDynamicToken) imageSource.system.tokenRingImg = data.selectedImage;
|
||||||
|
else imageSource.system.tokenImg = data.selectedImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve({ selected: selected, evolved: app.evolved, hybrid: app.hybrid, item: featureItem });
|
resolve({
|
||||||
|
selected: selected,
|
||||||
|
evolved: { ...app.evolved, form: evolved },
|
||||||
|
hybrid: app.hybrid,
|
||||||
|
item: featureItem
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.roll = this.roll;
|
context.roll = this.roll;
|
||||||
context.rollType = this.roll?.constructor.name;
|
context.rollType = this.roll?.constructor.name;
|
||||||
context.rallyDie = this.roll.rallyChoices;
|
context.rallyDie = this.roll.rallyChoices;
|
||||||
const experiences = this.config.data?.experiences || {};
|
const experiences = this.config.data?.system?.experiences || {};
|
||||||
context.experiences = Object.keys(experiences).map(id => ({
|
context.experiences = Object.keys(experiences).map(id => ({
|
||||||
id,
|
id,
|
||||||
...experiences[id]
|
...experiences[id]
|
||||||
|
|
@ -116,14 +116,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.isLite = this.config.roll?.lite;
|
context.isLite = this.config.roll?.lite;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
if (this.actor.system.traits) context.abilities = this.getTraitModifiers();
|
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
|
||||||
|
|
||||||
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
||||||
context.reactionOverride = this.reactionOverride;
|
context.reactionOverride = this.reactionOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
if (tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
|
if (this.actor && tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
|
||||||
context.activeTagTeamRoll = true;
|
context.activeTagTeamRoll = true;
|
||||||
context.tagTeamSelected = this.config.tagTeamSelected;
|
context.tagTeamSelected = this.config.tagTeamSelected;
|
||||||
}
|
}
|
||||||
|
|
@ -185,7 +185,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
extKey: button.dataset.key,
|
extKey: button.dataset.key,
|
||||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||||
value: 1,
|
value: 1,
|
||||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
this.render();
|
this.render();
|
||||||
|
|
@ -195,9 +195,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
if (this.config.roll) {
|
if (this.config.roll) {
|
||||||
this.reactionOverride = !this.reactionOverride;
|
this.reactionOverride = !this.reactionOverride;
|
||||||
this.config.actionType = this.reactionOverride
|
this.config.actionType = this.reactionOverride
|
||||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
? 'reaction'
|
||||||
: this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
|
: this.config.actionType === 'reaction'
|
||||||
? null
|
? 'action'
|
||||||
: this.config.actionType;
|
: this.config.actionType;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
@ -91,14 +93,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
getRefreshables() {
|
getRefreshables() {
|
||||||
const actionItems = this.actor.items.reduce((acc, x) => {
|
const actionItems = this.actor.items.filter(x => this.actor.system.isItemAvailable(x)).reduce((acc, x) => {
|
||||||
if (x.system.actions) {
|
if (x.system.actions) {
|
||||||
const recoverable = x.system.actions.reduce((acc, action) => {
|
const recoverable = x.system.actions.reduce((acc, action) => {
|
||||||
if (
|
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {
|
||||||
action.uses.recovery &&
|
|
||||||
((action.uses.recovery === 'longRest' && !this.shortrest) ||
|
|
||||||
action.uses.recovery === 'shortRest')
|
|
||||||
) {
|
|
||||||
acc.push({
|
acc.push({
|
||||||
title: x.name,
|
title: x.name,
|
||||||
name: action.name,
|
name: action.name,
|
||||||
|
|
@ -120,8 +118,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
if (
|
if (
|
||||||
x.system.resource &&
|
x.system.resource &&
|
||||||
x.system.resource.type &&
|
x.system.resource.type &&
|
||||||
((x.system.resource.recovery === 'longRest') === !this.shortrest ||
|
refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], x.system.resource.recovery)
|
||||||
x.system.resource.recovery === 'shortRest')
|
|
||||||
) {
|
) {
|
||||||
acc.push({
|
acc.push({
|
||||||
title: game.i18n.localize(`TYPES.Item.${x.type}`),
|
title: game.i18n.localize(`TYPES.Item.${x.type}`),
|
||||||
|
|
@ -184,12 +181,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
.filter(x => category.moves[x].selected)
|
.filter(x => category.moves[x].selected)
|
||||||
.flatMap(key => {
|
.flatMap(key => {
|
||||||
const move = category.moves[key];
|
const move = category.moves[key];
|
||||||
|
const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0;
|
||||||
return [...Array(move.selected).keys()].map(_ => ({
|
return [...Array(move.selected).keys()].map(_ => ({
|
||||||
...move,
|
...move,
|
||||||
movePath: `${categoryKey}.moves.${key}`
|
movePath: `${categoryKey}.moves.${key}`,
|
||||||
|
needsTarget: needsTarget
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
const characters = game.actors.filter(x => x.type === 'character')
|
||||||
|
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
|
||||||
|
.filter(x => x.uuid !== this.actor.uuid);
|
||||||
|
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const msg = {
|
const msg = {
|
||||||
|
|
@ -209,7 +211,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
||||||
),
|
),
|
||||||
actor: { name: this.actor.name, img: this.actor.img },
|
actor: { name: this.actor.name, img: this.actor.img },
|
||||||
moves: moves
|
moves: moves,
|
||||||
|
characters: characters,
|
||||||
|
selfId: this.actor.uuid
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
flags: {
|
flags: {
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,61 @@
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
constructor(item) {
|
constructor(data) {
|
||||||
super({});
|
super({});
|
||||||
|
this.data = data;
|
||||||
this.item = item;
|
|
||||||
this.quantity = item.system.quantity;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return this.item.name;
|
return this.data.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
|
||||||
position: { width: 300, height: 'auto' },
|
position: { width: 400, height: 'auto' },
|
||||||
window: { icon: 'fa-solid fa-hand-holding-hand' },
|
window: { icon: 'fa-solid fa-hand-holding-hand' },
|
||||||
actions: {
|
actions: {
|
||||||
finish: ItemTransferDialog.#finish
|
finish: ItemTransferDialog.#finish
|
||||||
},
|
}
|
||||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs' }
|
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
|
||||||
|
|
||||||
htmlElement.querySelector('.number-display').addEventListener('change', event => {
|
|
||||||
this.quantity = isNaN(event.target.value) ? this.quantity : Number(event.target.value);
|
|
||||||
this.render();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.item = this.item;
|
return foundry.utils.mergeObject(context, this.data);
|
||||||
context.quantity = this.quantity;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updateData(_event, _element, formData) {
|
|
||||||
const { quantity } = foundry.utils.expandObject(formData.object);
|
|
||||||
this.quantity = quantity;
|
|
||||||
this.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #finish() {
|
static async #finish() {
|
||||||
this.close({ submitted: true });
|
this.selected = this.form.elements.quantity.valueAsNumber || null;
|
||||||
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
close(options = {}) {
|
static #determineTransferOptions({ originActor, targetActor, item, currency }) {
|
||||||
if (!options.submitted) this.quantity = null;
|
originActor ??= item?.actor;
|
||||||
|
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||||
|
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||||
|
|
||||||
super.close();
|
return {
|
||||||
|
originActor,
|
||||||
|
targetActor,
|
||||||
|
itemImage: item?.img,
|
||||||
|
currencyIcon: currencySetting?.icon,
|
||||||
|
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
||||||
|
title: item?.name ?? currencySetting?.label
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static async configure(item) {
|
static async configure(options) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const app = new this(item);
|
const data = this.#determineTransferOptions(options);
|
||||||
app.addEventListener('close', () => resolve(app.quantity), { once: true });
|
if (data.max <= 1) return resolve(data.max);
|
||||||
|
|
||||||
|
const app = new this(data);
|
||||||
|
app.addEventListener('close', () => resolve(app.selected), { once: true });
|
||||||
app.render({ force: true });
|
app.render({ force: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
|
||||||
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
|
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
|
||||||
context.defaultOwnership = this.defaultOwnership;
|
context.defaultOwnership = this.defaultOwnership;
|
||||||
context.ownership = game.users.reduce((acc, user) => {
|
context.ownership = game.users.reduce((acc, user) => {
|
||||||
|
|
@ -52,6 +51,7 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
context.showOwnership = Boolean(Object.keys(context.ownership).length);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
||||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -76,28 +77,37 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
cost: this.data.initiator.cost
|
cost: this.data.initiator.cost
|
||||||
};
|
};
|
||||||
|
|
||||||
context.selectedData = Object.values(context.members).reduce(
|
const selectedMember = Object.values(context.members).find(x => x.selected && x.roll);
|
||||||
(acc, member) => {
|
const selectedIsCritical = selectedMember?.roll?.system?.isCritical;
|
||||||
if (!member.roll) return acc;
|
context.selectedData = {
|
||||||
if (member.selected) {
|
result: selectedMember
|
||||||
acc.result = `${member.roll.system.roll.total} ${member.roll.system.roll.result.label}`;
|
? `${selectedMember.roll.system.roll.total} ${selectedMember.roll.system.roll.result.label}`
|
||||||
}
|
: null,
|
||||||
|
damageValues: null
|
||||||
|
};
|
||||||
|
|
||||||
if (context.usesDamage) {
|
for (const member of Object.values(context.members)) {
|
||||||
if (!acc.damageValues) acc.damageValues = {};
|
if (!member.roll) continue;
|
||||||
for (let damage of member.damageValues) {
|
if (context.usesDamage) {
|
||||||
if (acc.damageValues[damage.key]) {
|
if (!context.selectedData.damageValues) context.selectedData.damageValues = {};
|
||||||
acc.damageValues[damage.key].total += damage.total;
|
for (let damage of member.damageValues) {
|
||||||
} else {
|
const damageTotal = member.roll.system.isCritical
|
||||||
acc.damageValues[damage.key] = foundry.utils.deepClone(damage);
|
? damage.total
|
||||||
}
|
: selectedIsCritical
|
||||||
|
? damage.total + (await getCritDamageBonus(member.roll.system.damage[damage.key].formula))
|
||||||
|
: damage.total;
|
||||||
|
if (context.selectedData.damageValues[damage.key]) {
|
||||||
|
context.selectedData.damageValues[damage.key].total += damageTotal;
|
||||||
|
} else {
|
||||||
|
context.selectedData.damageValues[damage.key] = {
|
||||||
|
...foundry.utils.deepClone(damage),
|
||||||
|
total: damageTotal
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{ result: null, damageValues: null }
|
|
||||||
);
|
|
||||||
context.showResult = Object.values(context.members).reduce((enabled, member) => {
|
context.showResult = Object.values(context.members).reduce((enabled, member) => {
|
||||||
if (!member.roll) return enabled;
|
if (!member.roll) return enabled;
|
||||||
if (context.usesDamage) {
|
if (context.usesDamage) {
|
||||||
|
|
@ -201,21 +211,41 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
.map(key => game.messages.get(this.data.members[key].messageId));
|
.map(key => game.messages.get(this.data.members[key].messageId));
|
||||||
|
|
||||||
const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
|
const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
|
||||||
|
const criticalRoll = systemData.roll.isCritical;
|
||||||
for (let roll of secondaryRolls) {
|
for (let roll of secondaryRolls) {
|
||||||
if (roll.system.hasDamage) {
|
if (roll.system.hasDamage) {
|
||||||
for (let key in roll.system.damage) {
|
for (let key in roll.system.damage) {
|
||||||
var damage = roll.system.damage[key];
|
var damage = roll.system.damage[key];
|
||||||
|
const damageTotal =
|
||||||
|
!roll.system.isCritical && criticalRoll
|
||||||
|
? (await getCritDamageBonus(damage.formula)) + damage.total
|
||||||
|
: damage.total;
|
||||||
|
const updatedDamageParts = damage.parts;
|
||||||
if (systemData.damage[key]) {
|
if (systemData.damage[key]) {
|
||||||
systemData.damage[key].total += damage.total;
|
if (!roll.system.isCritical && criticalRoll) {
|
||||||
systemData.damage[key].parts = [...systemData.damage[key].parts, ...damage.parts];
|
for (let part of updatedDamageParts) {
|
||||||
|
const criticalDamage = await getCritDamageBonus(part.formula);
|
||||||
|
if (criticalDamage) {
|
||||||
|
damage.formula = `${damage.formula} + ${criticalDamage}`;
|
||||||
|
part.formula = `${part.formula} + ${criticalDamage}`;
|
||||||
|
part.modifierTotal = part.modifierTotal + criticalDamage;
|
||||||
|
part.total += criticalDamage;
|
||||||
|
part.roll = new Roll(part.formula);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`;
|
||||||
|
systemData.damage[key].total += damageTotal;
|
||||||
|
systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts];
|
||||||
} else {
|
} else {
|
||||||
systemData.damage[key] = damage;
|
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
|
||||||
|
|
||||||
|
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||||
const cls = getDocumentClass('ChatMessage'),
|
const cls = getDocumentClass('ChatMessage'),
|
||||||
msgData = {
|
msgData = {
|
||||||
type: 'dualityRoll',
|
type: 'dualityRoll',
|
||||||
|
|
@ -233,14 +263,16 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
||||||
for (let memberId of Object.keys(this.data.members)) {
|
for (let memberId of Object.keys(this.data.members)) {
|
||||||
const resourceUpdates = [];
|
const resourceUpdates = [];
|
||||||
if (systemData.roll.isCritical || systemData.roll.result.duality === 1) {
|
const rollGivesHope = systemData.roll.isCritical || systemData.roll.result.duality === 1;
|
||||||
const value =
|
if (memberId === this.data.initiator.id) {
|
||||||
memberId !== this.data.initiator.id
|
const value = this.data.initiator.cost
|
||||||
? 1
|
? rollGivesHope
|
||||||
: this.data.initiator.cost
|
? 1 - this.data.initiator.cost
|
||||||
? 1 - this.data.initiator.cost
|
: -this.data.initiator.cost
|
||||||
: 1;
|
: 1;
|
||||||
resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true });
|
resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true });
|
||||||
|
} else if (rollGivesHope) {
|
||||||
|
resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true });
|
||||||
}
|
}
|
||||||
if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||||
if (systemData.roll.result.duality === -1) {
|
if (systemData.roll.result.duality === -1) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
|
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
|
if (!this.actor) return context;
|
||||||
|
|
||||||
context.partyOnCanvas =
|
context.partyOnCanvas =
|
||||||
this.actor.type === 'party' &&
|
this.actor.type === 'party' &&
|
||||||
|
|
@ -31,6 +32,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||||
? false
|
? false
|
||||||
: context.canToggleCombat;
|
: context.canToggleCombat;
|
||||||
|
|
||||||
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
|
||||||
const effect = context.statusEffects[key];
|
const effect = context.statusEffects[key];
|
||||||
if (effect.systemEffect) {
|
if (effect.systemEffect) {
|
||||||
|
|
@ -58,14 +60,33 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #onToggleCombat() {
|
static async #onToggleCombat() {
|
||||||
|
const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
|
||||||
|
const warning =
|
||||||
|
tokensWithoutActors.length === 1
|
||||||
|
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
|
||||||
|
name: tokensWithoutActors[0].name
|
||||||
|
})
|
||||||
|
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
|
||||||
|
names: tokensWithoutActors.map(x => x.name).join(', ')
|
||||||
|
});
|
||||||
|
|
||||||
const tokens = canvas.tokens.controlled
|
const tokens = canvas.tokens.controlled
|
||||||
.filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||||
.map(t => t.document);
|
.map(t => t.document);
|
||||||
if (!this.object.controlled) tokens.push(this.document);
|
if (!this.object.controlled && this.document.actor) tokens.push(this.document);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
|
if (this.document.inCombat) {
|
||||||
else await TokenDocument.implementation.createCombatants(tokens);
|
const tokensInCombat = tokens.filter(t => t.inCombat);
|
||||||
|
await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
|
||||||
|
} else {
|
||||||
|
if (tokensWithoutActors.length) {
|
||||||
|
ui.notifications.warn(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
|
||||||
|
await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ui.notifications.warn(err.message);
|
ui.notifications.warn(err.message);
|
||||||
}
|
}
|
||||||
|
|
@ -193,16 +214,20 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the status of effects which are active for the token actor
|
// Update the status of effects which are active for the token actor
|
||||||
const activeEffects = this.actor?.effects || [];
|
const activeEffects = this.actor?.getActiveEffects() || [];
|
||||||
for (const effect of activeEffects) {
|
for (const effect of activeEffects) {
|
||||||
for (const statusId of effect.statuses) {
|
for (const statusId of effect.statuses) {
|
||||||
const status = choices[statusId];
|
const status = choices[statusId];
|
||||||
if (!status) continue;
|
if (!status) continue;
|
||||||
|
|
||||||
|
status.instances = 1 + (status.instances ?? 0);
|
||||||
|
status.locked = status.locked || effect.condition || status.instances > 1;
|
||||||
|
if (!status) continue;
|
||||||
if (status._id) {
|
if (status._id) {
|
||||||
if (status._id !== effect.id) continue;
|
if (status._id !== effect.id) continue;
|
||||||
}
|
}
|
||||||
status.isActive = true;
|
status.isActive = true;
|
||||||
if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
|
if (effect.getFlag?.('core', 'overlay')) status.isOverlay = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -280,11 +280,19 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
||||||
break;
|
break;
|
||||||
case 'experience':
|
case 'experience':
|
||||||
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
if (!advancement[choiceKey]) advancement[choiceKey] = [];
|
||||||
|
const allExperiences = {
|
||||||
|
...this.actor.system.experiences,
|
||||||
|
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||||
|
for (const key of Object.keys(level.achievements.experiences)) {
|
||||||
|
acc[key] = level.achievements.experiences[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
const data = checkbox.data.map(data => {
|
const data = checkbox.data.map(data => {
|
||||||
const experience = Object.keys(this.actor.system.experiences).find(
|
const experience = Object.keys(allExperiences).find(x => x === data);
|
||||||
x => x === data
|
return allExperiences[experience]?.name ?? '';
|
||||||
);
|
|
||||||
return this.actor.system.experiences[experience]?.name ?? '';
|
|
||||||
});
|
});
|
||||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -357,11 +357,23 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
||||||
|
|
||||||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||||
if (experienceIncreaseTagify) {
|
if (experienceIncreaseTagify) {
|
||||||
|
const allExperiences = {
|
||||||
|
...this.actor.system.experiences,
|
||||||
|
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||||
|
for (const key of Object.keys(level.achievements.experiences)) {
|
||||||
|
acc[key] = level.achievements.experiences[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
};
|
||||||
tagifyElement(
|
tagifyElement(
|
||||||
experienceIncreaseTagify,
|
experienceIncreaseTagify,
|
||||||
Object.keys(this.actor.system.experiences).reduce((acc, id) => {
|
Object.keys(allExperiences).reduce((acc, id) => {
|
||||||
const experience = this.actor.system.experiences[id];
|
const experience = allExperiences[id];
|
||||||
acc.push({ id: id, label: experience.name });
|
if (experience.name) {
|
||||||
|
acc.push({ id: id, label: experience.name });
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []),
|
}, []),
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
icon: 'fa-solid fa-gears'
|
icon: 'fa-solid fa-gears'
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
editCurrencyIcon: this.changeCurrencyIcon,
|
||||||
addItem: this.addItem,
|
addItem: this.addItem,
|
||||||
editItem: this.editItem,
|
editItem: this.editItem,
|
||||||
removeItem: this.removeItem,
|
removeItem: this.removeItem,
|
||||||
|
|
@ -43,6 +44,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
deleteAdversaryType: this.deleteAdversaryType,
|
deleteAdversaryType: this.deleteAdversaryType,
|
||||||
selectAdversaryType: this.selectAdversaryType,
|
selectAdversaryType: this.selectAdversaryType,
|
||||||
save: this.save,
|
save: this.save,
|
||||||
|
resetTokenSizes: this.resetTokenSizes,
|
||||||
reset: this.reset
|
reset: this.reset
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
form: { handler: this.updateData, submitOnChange: true }
|
||||||
|
|
@ -115,6 +117,45 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async changeCurrencyIcon(_, target) {
|
||||||
|
const type = target.dataset.currency;
|
||||||
|
const currentIcon = this.settings.currency[type].icon;
|
||||||
|
const icon = await foundry.applications.api.DialogV2.input({
|
||||||
|
classes: ['daggerheart', 'dh-style', 'change-currency-icon'],
|
||||||
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
'systems/daggerheart/templates/settings/homebrew-settings/change-currency-icon.hbs',
|
||||||
|
{ currentIcon }
|
||||||
|
),
|
||||||
|
window: {
|
||||||
|
title: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.currency.changeIcon'),
|
||||||
|
icon: 'fa-solid fa-coins'
|
||||||
|
},
|
||||||
|
render: (_, dialog) => {
|
||||||
|
const icon = dialog.element.querySelector('.displayed-icon i');
|
||||||
|
const input = dialog.element.querySelector('input');
|
||||||
|
const reset = dialog.element.querySelector('button[data-action=reset]');
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
icon.classList.value = input.value;
|
||||||
|
});
|
||||||
|
reset.addEventListener('click', () => {
|
||||||
|
const currencyField = DhHomebrew.schema.fields.currency.fields[type];
|
||||||
|
const initial = currencyField.fields.icon.getInitialValue();
|
||||||
|
input.value = icon.classList.value = initial;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
ok: {
|
||||||
|
callback: (_, button) => button.form.elements.icon.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (icon !== null) {
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`currency.${type}.icon`]: icon
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async addItem(_, target) {
|
static async addItem(_, target) {
|
||||||
const { type } = target.dataset;
|
const { type } = target.dataset;
|
||||||
if (['shortRest', 'longRest'].includes(type)) {
|
if (['shortRest', 'longRest'].includes(type)) {
|
||||||
|
|
@ -384,6 +425,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async resetTokenSizes() {
|
||||||
|
await this.settings.updateSource({
|
||||||
|
tokenSizes: this.settings.schema.fields.tokenSizes.initial
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async reset() {
|
static async reset() {
|
||||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
window: {
|
window: {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export { default as ActionConfig } from './action-config.mjs';
|
export { default as ActionConfig } from './action-config.mjs';
|
||||||
|
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||||
export { default as CharacterSettings } from './character-settings.mjs';
|
export { default as CharacterSettings } from './character-settings.mjs';
|
||||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||||
|
|
|
||||||
236
module/applications/sheets-configs/action-base-config.mjs
Normal file
236
module/applications/sheets-configs/action-base-config.mjs
Normal file
|
|
@ -0,0 +1,236 @@
|
||||||
|
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
||||||
|
|
||||||
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
|
constructor(action) {
|
||||||
|
super({});
|
||||||
|
|
||||||
|
this.action = action;
|
||||||
|
this.openSection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
|
||||||
|
window: {
|
||||||
|
icon: 'fa-solid fa-wrench',
|
||||||
|
resizable: false
|
||||||
|
},
|
||||||
|
position: { width: 600, height: 'auto' },
|
||||||
|
actions: {
|
||||||
|
toggleSection: this.toggleSection,
|
||||||
|
addEffect: this.addEffect,
|
||||||
|
removeEffect: this.removeEffect,
|
||||||
|
addElement: this.addElement,
|
||||||
|
removeElement: this.removeElement,
|
||||||
|
editEffect: this.editEffect,
|
||||||
|
addDamage: this.addDamage,
|
||||||
|
removeDamage: this.removeDamage
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
handler: this.updateForm,
|
||||||
|
submitOnChange: true,
|
||||||
|
closeOnSubmit: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
header: {
|
||||||
|
id: 'header',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/header.hbs'
|
||||||
|
},
|
||||||
|
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||||
|
base: {
|
||||||
|
id: 'base',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/base.hbs'
|
||||||
|
},
|
||||||
|
configuration: {
|
||||||
|
id: 'configuration',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs'
|
||||||
|
},
|
||||||
|
effect: {
|
||||||
|
id: 'effect',
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static TABS = {
|
||||||
|
base: {
|
||||||
|
active: true,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'base',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.base'
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'config',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
|
||||||
|
},
|
||||||
|
effect: {
|
||||||
|
active: false,
|
||||||
|
cssClass: '',
|
||||||
|
group: 'primary',
|
||||||
|
id: 'effect',
|
||||||
|
icon: null,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Tabs.effects'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
|
||||||
|
|
||||||
|
_getTabs(tabs) {
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
||||||
|
v.cssClass = v.active ? 'active' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options, 'action');
|
||||||
|
context.source = this.action.toObject(true);
|
||||||
|
context.openSection = this.openSection;
|
||||||
|
context.tabs = this._getTabs(this.constructor.TABS);
|
||||||
|
context.config = CONFIG.DH;
|
||||||
|
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
|
context.costOptions = this.getCostOptions();
|
||||||
|
context.getRollTypeOptions = this.getRollTypeOptions();
|
||||||
|
context.disableOption = this.disableOption.bind(this);
|
||||||
|
context.isNPC = this.action.actor?.isNPC;
|
||||||
|
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
|
||||||
|
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
|
||||||
|
context.hasRoll = this.action.hasRoll;
|
||||||
|
|
||||||
|
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||||
|
context.tierOptions = [
|
||||||
|
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
|
||||||
|
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
|
||||||
|
];
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static toggleSection(_, button) {
|
||||||
|
this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
|
||||||
|
this.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCostOptions() {
|
||||||
|
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
||||||
|
const resource = this.action.parent.resource;
|
||||||
|
if (resource) {
|
||||||
|
options.resource = {
|
||||||
|
label: 'DAGGERHEART.GENERAL.itemResource',
|
||||||
|
group: 'Global'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.action.parent.metadata?.isQuantifiable) {
|
||||||
|
options.quantity = {
|
||||||
|
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||||
|
group: 'Global'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRollTypeOptions() {
|
||||||
|
const types = foundry.utils.deepClone(CONFIG.DH.GENERAL.rollTypes);
|
||||||
|
if (!this.action.actor) return types;
|
||||||
|
Object.values(types).forEach(t => {
|
||||||
|
if (this.action.actor.type !== 'character' && t.playerOnly) delete types[t.id];
|
||||||
|
});
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
disableOption(index, costOptions, choices) {
|
||||||
|
const filtered = foundry.utils.deepClone(costOptions);
|
||||||
|
Object.keys(filtered).forEach(o => {
|
||||||
|
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
|
||||||
|
});
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
_prepareSubmitData(_event, formData) {
|
||||||
|
const submitData = foundry.utils.expandObject(formData.object);
|
||||||
|
|
||||||
|
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
|
||||||
|
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
||||||
|
const data = foundry.utils.getProperty(submitData, keyPath);
|
||||||
|
const dataValues = data ? Object.values(data) : [];
|
||||||
|
if (keyPath === 'cost') {
|
||||||
|
for (var value of dataValues) {
|
||||||
|
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
|
||||||
|
}
|
||||||
|
return submitData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateForm(event, _, formData) {
|
||||||
|
const submitData = this._prepareSubmitData(event, formData),
|
||||||
|
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||||
|
this.action = await this.action.update(data);
|
||||||
|
|
||||||
|
this.sheetUpdate?.(this.action);
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
static addElement(event) {
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
key = event.target.closest('[data-key]').dataset.key;
|
||||||
|
if (!this.action[key]) return;
|
||||||
|
|
||||||
|
data[key].push(this.action.defaultValues[key] ?? {});
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeElement(event, button) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
key = event.target.closest('[data-key]').dataset.key,
|
||||||
|
index = button.dataset.index;
|
||||||
|
data[key].splice(index, 1);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static addDamage(_event) {
|
||||||
|
if (!this.action.damage.parts) return;
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
part = {};
|
||||||
|
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
||||||
|
data.damage.parts.push(part);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeDamage(_event, button) {
|
||||||
|
if (!this.action.damage.parts) return;
|
||||||
|
const data = this.action.toObject(),
|
||||||
|
index = button.dataset.index;
|
||||||
|
data.damage.parts.splice(index, 1);
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Specific implementation in extending classes **/
|
||||||
|
static async addEffect(_event) {}
|
||||||
|
static removeEffect(_event, _button) {}
|
||||||
|
static editEffect(_event) {}
|
||||||
|
|
||||||
|
async close(options) {
|
||||||
|
this.tabGroups.primary = 'base';
|
||||||
|
await super.close(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,241 +1,32 @@
|
||||||
import DaggerheartSheet from '../sheets/daggerheart-sheet.mjs';
|
import DHActionBaseConfig from './action-base-config.mjs';
|
||||||
|
|
||||||
const { ApplicationV2 } = foundry.applications.api;
|
|
||||||
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
|
||||||
constructor(action, sheetUpdate) {
|
|
||||||
super({});
|
|
||||||
|
|
||||||
this.action = action;
|
|
||||||
this.sheetUpdate = sheetUpdate;
|
|
||||||
this.openSection = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get title() {
|
|
||||||
return `${game.i18n.localize('DAGGERHEART.GENERAL.Tabs.settings')}: ${this.action.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export default class DHActionConfig extends DHActionBaseConfig {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
tag: 'form',
|
...DHActionBaseConfig.DEFAULT_OPTIONS,
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'max-800'],
|
|
||||||
window: {
|
|
||||||
icon: 'fa-solid fa-wrench',
|
|
||||||
resizable: false
|
|
||||||
},
|
|
||||||
position: { width: 600, height: 'auto' },
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleSection: this.toggleSection,
|
...DHActionBaseConfig.DEFAULT_OPTIONS.actions,
|
||||||
addEffect: this.addEffect,
|
addEffect: this.addEffect,
|
||||||
removeEffect: this.removeEffect,
|
removeEffect: this.removeEffect,
|
||||||
addElement: this.addElement,
|
editEffect: this.editEffect
|
||||||
removeElement: this.removeElement,
|
|
||||||
editEffect: this.editEffect,
|
|
||||||
addDamage: this.addDamage,
|
|
||||||
removeDamage: this.removeDamage
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
handler: this.updateForm,
|
|
||||||
submitOnChange: true,
|
|
||||||
closeOnSubmit: false
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
async _prepareContext(options) {
|
||||||
header: {
|
const context = await super._prepareContext(options);
|
||||||
id: 'header',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/header.hbs'
|
|
||||||
},
|
|
||||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
|
||||||
base: {
|
|
||||||
id: 'base',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/base.hbs'
|
|
||||||
},
|
|
||||||
configuration: {
|
|
||||||
id: 'configuration',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/configuration.hbs'
|
|
||||||
},
|
|
||||||
effect: {
|
|
||||||
id: 'effect',
|
|
||||||
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static TABS = {
|
|
||||||
base: {
|
|
||||||
active: true,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'primary',
|
|
||||||
id: 'base',
|
|
||||||
icon: null,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Tabs.base'
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
active: false,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'primary',
|
|
||||||
id: 'config',
|
|
||||||
icon: null,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Tabs.configuration'
|
|
||||||
},
|
|
||||||
effect: {
|
|
||||||
active: false,
|
|
||||||
cssClass: '',
|
|
||||||
group: 'primary',
|
|
||||||
id: 'effect',
|
|
||||||
icon: null,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Tabs.effects'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static CLEAN_ARRAYS = ['damage.parts', 'cost', 'effects'];
|
|
||||||
|
|
||||||
_getTabs(tabs) {
|
|
||||||
for (const v of Object.values(tabs)) {
|
|
||||||
v.active = this.tabGroups[v.group] ? this.tabGroups[v.group] === v.id : v.active;
|
|
||||||
v.cssClass = v.active ? 'active' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
return tabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options, 'action');
|
|
||||||
context.source = this.action.toObject(false);
|
|
||||||
context.openSection = this.openSection;
|
|
||||||
context.tabs = this._getTabs(this.constructor.TABS);
|
|
||||||
context.config = CONFIG.DH;
|
|
||||||
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
|
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
|
||||||
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
|
||||||
context.getEffectDetails = this.getEffectDetails.bind(this);
|
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||||
context.costOptions = this.getCostOptions();
|
|
||||||
context.getRollTypeOptions = this.getRollTypeOptions();
|
|
||||||
context.disableOption = this.disableOption.bind(this);
|
|
||||||
context.isNPC = this.action.actor?.isNPC;
|
|
||||||
context.baseSaveDifficulty = this.action.actor?.baseSaveDifficulty;
|
|
||||||
context.baseAttackBonus = this.action.actor?.system.attack?.roll.bonus;
|
|
||||||
context.hasRoll = this.action.hasRoll;
|
|
||||||
|
|
||||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
|
||||||
context.tierOptions = [
|
|
||||||
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
|
|
||||||
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
|
|
||||||
];
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static toggleSection(_, button) {
|
static async addEffect(_event) {
|
||||||
this.openSection = button.dataset.section === this.openSection ? null : button.dataset.section;
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
getCostOptions() {
|
|
||||||
const options = foundry.utils.deepClone(CONFIG.DH.GENERAL.abilityCosts);
|
|
||||||
const resource = this.action.parent.resource;
|
|
||||||
if (resource) {
|
|
||||||
options.resource = {
|
|
||||||
label: 'DAGGERHEART.GENERAL.itemResource',
|
|
||||||
group: 'Global'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.action.parent.metadata?.isQuantifiable) {
|
|
||||||
options.quantity = {
|
|
||||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
|
||||||
group: 'Global'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRollTypeOptions() {
|
|
||||||
const types = foundry.utils.deepClone(CONFIG.DH.GENERAL.rollTypes);
|
|
||||||
if (!this.action.actor) return types;
|
|
||||||
Object.values(types).forEach(t => {
|
|
||||||
if (this.action.actor.type !== 'character' && t.playerOnly) delete types[t.id];
|
|
||||||
});
|
|
||||||
return types;
|
|
||||||
}
|
|
||||||
|
|
||||||
disableOption(index, costOptions, choices) {
|
|
||||||
const filtered = foundry.utils.deepClone(costOptions);
|
|
||||||
Object.keys(filtered).forEach(o => {
|
|
||||||
if (choices.find((c, idx) => c.type === o && index !== idx)) filtered[o].disabled = true;
|
|
||||||
});
|
|
||||||
return filtered;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEffectDetails(id) {
|
|
||||||
return this.action.item.effects.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
_prepareSubmitData(_event, formData) {
|
|
||||||
const submitData = foundry.utils.expandObject(formData.object);
|
|
||||||
|
|
||||||
const itemAbilityCostKeys = Object.keys(CONFIG.DH.GENERAL.itemAbilityCosts);
|
|
||||||
for (const keyPath of this.constructor.CLEAN_ARRAYS) {
|
|
||||||
const data = foundry.utils.getProperty(submitData, keyPath);
|
|
||||||
const dataValues = data ? Object.values(data) : [];
|
|
||||||
if (keyPath === 'cost') {
|
|
||||||
for (var value of dataValues) {
|
|
||||||
value.itemId = itemAbilityCostKeys.includes(value.key) ? this.action.parent.parent.id : null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data) foundry.utils.setProperty(submitData, keyPath, dataValues);
|
|
||||||
}
|
|
||||||
return submitData;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updateForm(event, _, formData) {
|
|
||||||
const submitData = this._prepareSubmitData(event, formData),
|
|
||||||
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
|
||||||
this.action = await this.action.update(data);
|
|
||||||
|
|
||||||
this.sheetUpdate?.(this.action);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
static addElement(event) {
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
key = event.target.closest('[data-key]').dataset.key;
|
|
||||||
if (!this.action[key]) return;
|
|
||||||
|
|
||||||
data[key].push(this.action.defaultValues[key] ?? {});
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeElement(event, button) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
key = event.target.closest('[data-key]').dataset.key,
|
|
||||||
index = button.dataset.index;
|
|
||||||
data[key].splice(index, 1);
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static addDamage(event) {
|
|
||||||
if (!this.action.damage.parts) return;
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
part = {};
|
|
||||||
if (this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
|
|
||||||
data.damage.parts.push(part);
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static removeDamage(event, button) {
|
|
||||||
if (!this.action.damage.parts) return;
|
|
||||||
const data = this.action.toObject(),
|
|
||||||
index = button.dataset.index;
|
|
||||||
data.damage.parts.splice(index, 1);
|
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
|
||||||
}
|
|
||||||
|
|
||||||
static async addEffect(event) {
|
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = this._addEffectData.bind(this)(),
|
const effectData = this._addEffectData.bind(this)();
|
||||||
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
|
const data = this.action.toObject();
|
||||||
data = this.action.toObject();
|
|
||||||
|
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], {
|
||||||
|
render: false
|
||||||
|
});
|
||||||
data.effects.push({ _id: created._id });
|
data.effects.push({ _id: created._id });
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
this.action.item.effects.get(created._id).sheet.render(true);
|
this.action.item.effects.get(created._id).sheet.render(true);
|
||||||
|
|
@ -255,6 +46,10 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEffectDetails(id) {
|
||||||
|
return this.action.item.effects.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
static removeEffect(event, button) {
|
static removeEffect(event, button) {
|
||||||
if (!this.action.effects) return;
|
if (!this.action.effects) return;
|
||||||
const index = button.dataset.index,
|
const index = button.dataset.index,
|
||||||
|
|
@ -267,9 +62,4 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
||||||
this.action.item.effects.get(id).sheet.render(true);
|
this.action.item.effects.get(id).sheet.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
async close(options) {
|
|
||||||
this.tabGroups.primary = 'base';
|
|
||||||
await super.close(options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import DHActionBaseConfig from './action-base-config.mjs';
|
||||||
|
|
||||||
|
export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
||||||
|
constructor(action, effects, sheetUpdate) {
|
||||||
|
super(action);
|
||||||
|
|
||||||
|
this.effects = effects;
|
||||||
|
this.sheetUpdate = sheetUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
...DHActionBaseConfig.DEFAULT_OPTIONS,
|
||||||
|
actions: {
|
||||||
|
...DHActionBaseConfig.DEFAULT_OPTIONS.actions,
|
||||||
|
addEffect: this.addEffect,
|
||||||
|
removeEffect: this.removeEffect,
|
||||||
|
editEffect: this.editEffect
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.effects = this.effects;
|
||||||
|
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEffectDetails(id) {
|
||||||
|
return this.effects.find(x => x.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addEffect(_event) {
|
||||||
|
if (!this.action.effects) return;
|
||||||
|
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
||||||
|
const data = this.action.toObject();
|
||||||
|
|
||||||
|
this.sheetUpdate(data, effectData);
|
||||||
|
this.effects = [...this.effects, effectData];
|
||||||
|
data.effects.push({ _id: effectData.id });
|
||||||
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeEffect(event, button) {
|
||||||
|
if (!this.action.effects) return;
|
||||||
|
const index = button.dataset.index,
|
||||||
|
effectId = this.action.effects[index]._id;
|
||||||
|
this.constructor.removeElement.bind(this)(event, button);
|
||||||
|
this.sheetUpdate(
|
||||||
|
this.action.toObject(),
|
||||||
|
this.effects.find(x => x.id === effectId),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async editEffect(event) {
|
||||||
|
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
|
||||||
|
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(
|
||||||
|
this.getEffectDetails(id)
|
||||||
|
);
|
||||||
|
if (!updatedEffect) return;
|
||||||
|
|
||||||
|
this.effects = await this.sheetUpdate(this.action.toObject(), { ...updatedEffect, id });
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,9 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
if (!ignoredActorKeys.includes(key)) {
|
if (!ignoredActorKeys.includes(key)) {
|
||||||
const model = game.system.api.models.actors[key];
|
const model = game.system.api.models.actors[key];
|
||||||
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model);
|
||||||
|
// As per DHToken._getTrackedAttributesFromSchema, attributes.bar have a max version as well.
|
||||||
|
const maxAttributes = attributes.bar.map(x => [...x, 'max']);
|
||||||
|
attributes.value.push(...maxAttributes);
|
||||||
const group = game.i18n.localize(model.metadata.label);
|
const group = game.i18n.localize(model.metadata.label);
|
||||||
const choices = CONFIG.Token.documentClass
|
const choices = CONFIG.Token.documentClass
|
||||||
.getTrackedAttributeChoices(attributes, model)
|
.getTrackedAttributeChoices(attributes, model)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,19 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
const featureForms = ['passive', 'action', 'reaction'];
|
||||||
|
context.features = context.document.system.features.sort((a, b) =>
|
||||||
|
a.system.featureForm !== b.system.featureForm
|
||||||
|
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||||
|
: a.sort - b.sort
|
||||||
|
);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -98,16 +111,16 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
if (item?.type === 'feature') {
|
if (item?.type === 'feature') {
|
||||||
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemData = item.toObject();
|
const itemData = item.toObject();
|
||||||
delete itemData._id;
|
delete itemData._id;
|
||||||
|
|
||||||
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,19 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
const featureForms = ['passive', 'action', 'reaction'];
|
||||||
|
context.features = context.document.system.features.sort((a, b) =>
|
||||||
|
a.system.featureForm !== b.system.featureForm
|
||||||
|
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||||
|
: a.sort - b.sort
|
||||||
|
);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new category entry to the actor.
|
* Adds a new category entry to the actor.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -109,9 +122,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.fromInternal) return;
|
|
||||||
|
|
||||||
const item = await fromUuid(data.uuid);
|
const item = await fromUuid(data.uuid);
|
||||||
|
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
||||||
|
|
||||||
if (item.type === 'adversary' && event.target.closest('.category-container')) {
|
if (item.type === 'adversary' && event.target.closest('.category-container')) {
|
||||||
const target = event.target.closest('.category-container');
|
const target = event.target.closest('.category-container');
|
||||||
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
const path = `system.potentialAdversaries.${target.dataset.potentialAdversary}.adversaries`;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,30 @@
|
||||||
export default class DhPrototypeTokenConfig extends foundry.applications.sheets.PrototypeTokenConfig {
|
import DHTokenConfigMixin from './token-config-mixin.mjs';
|
||||||
|
import { getActorSizeFromForm } from './token-config-mixin.mjs';
|
||||||
|
|
||||||
|
export default class DhPrototypeTokenConfig extends DHTokenConfigMixin(
|
||||||
|
foundry.applications.sheets.PrototypeTokenConfig
|
||||||
|
) {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _prepareResourcesTab() {
|
static DEFAULT_OPTIONS = {
|
||||||
const token = this.token;
|
...super.DEFAULT_OPTIONS,
|
||||||
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
form: { handler: DhPrototypeTokenConfig.#onSubmit }
|
||||||
const attributeSource =
|
};
|
||||||
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
|
||||||
? this.actor?.type
|
/**
|
||||||
: this.actor?.system;
|
* Process form submission for the sheet
|
||||||
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
* @this {PrototypeTokenConfig}
|
||||||
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
* @type {ApplicationFormSubmission}
|
||||||
return {
|
*/
|
||||||
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
static async #onSubmit(event, form, formData) {
|
||||||
bar1: token.getBarAttribute?.('bar1'),
|
const submitData = this._processFormData(event, form, formData);
|
||||||
bar2: token.getBarAttribute?.('bar2'),
|
submitData.detectionModes ??= []; // Clear detection modes array
|
||||||
turnMarkerModes: DhPrototypeTokenConfig.TURN_MARKER_MODES,
|
this._processChanges(submitData);
|
||||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
const changes = { prototypeToken: submitData };
|
||||||
};
|
|
||||||
|
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
|
||||||
|
if (changedTokenSizeValue) changes.system = { size: changedTokenSizeValue };
|
||||||
|
|
||||||
|
this.actor.validate({ changes, clean: true, fallback: false });
|
||||||
|
await this.actor.update(changes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config'],
|
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'],
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
position: {
|
position: {
|
||||||
width: 560
|
width: 560
|
||||||
|
|
@ -131,6 +131,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
if (partId in context.tabs) context.tab = context.tabs[partId];
|
if (partId in context.tabs) context.tab = context.tabs[partId];
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
case 'details':
|
case 'details':
|
||||||
|
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
|
||||||
context.isActorEffect = false;
|
context.isActorEffect = false;
|
||||||
context.isItemEffect = true;
|
context.isItemEffect = true;
|
||||||
const useGeneric = game.settings.get(
|
const useGeneric = game.settings.get(
|
||||||
|
|
@ -138,10 +139,13 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
).showGenericStatusEffects;
|
).showGenericStatusEffects;
|
||||||
if (!useGeneric) {
|
if (!useGeneric) {
|
||||||
context.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
context.statuses = [
|
||||||
value: status.id,
|
...context.statuses,
|
||||||
label: game.i18n.localize(status.name)
|
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
||||||
}));
|
value: status.id,
|
||||||
|
label: game.i18n.localize(status.name)
|
||||||
|
}))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'changes':
|
case 'changes':
|
||||||
|
|
@ -157,7 +161,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #onSubmit(event, form, formData) {
|
static async #onSubmit(_event, _form, formData) {
|
||||||
this.data = foundry.utils.expandObject(formData.object);
|
this.data = foundry.utils.expandObject(formData.object);
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
@ -193,11 +197,11 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
*/
|
*/
|
||||||
static async #addChange() {
|
static async #addChange() {
|
||||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||||
const changes = Object.values(submitData.changes ?? {});
|
const updatedChanges = Object.values(changes ?? {});
|
||||||
changes.push({});
|
updatedChanges.push({});
|
||||||
|
|
||||||
this.effect.changes = changes;
|
this.effect = { ...rest, changes: updatedChanges };
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,12 +212,12 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
||||||
*/
|
*/
|
||||||
static async #deleteChange(event) {
|
static async #deleteChange(event) {
|
||||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||||
const changes = Object.values(submitData.changes);
|
const updatedChanges = Object.values(submitData.changes);
|
||||||
const row = event.target.closest('li');
|
const row = event.target.closest('li');
|
||||||
const index = Number(row.dataset.index) || 0;
|
const index = Number(row.dataset.index) || 0;
|
||||||
changes.splice(index, 1);
|
updatedChanges.splice(index, 1);
|
||||||
|
|
||||||
this.effect.changes = changes;
|
this.effect = { ...submitData, changes: updatedChanges };
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { actionsTypes } from '../../data/action/_module.mjs';
|
import { actionsTypes } from '../../data/action/_module.mjs';
|
||||||
import DHActionConfig from './action-config.mjs';
|
import ActionSettingsConfig from './action-settings-config.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -102,6 +102,8 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
return (
|
return (
|
||||||
(await foundry.applications.api.DialogV2.input({
|
(await foundry.applications.api.DialogV2.input({
|
||||||
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
|
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
|
||||||
|
position: { width: 300 },
|
||||||
|
classes: ['daggerheart', 'dh-style'],
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
||||||
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
||||||
|
|
@ -158,16 +160,55 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
||||||
this.render();
|
this.render();
|
||||||
} else {
|
} else {
|
||||||
const action = this.move.actions.get(id);
|
const action = this.move.actions.get(id);
|
||||||
await new DHActionConfig(action, async updatedMove => {
|
await new ActionSettingsConfig(action, this.move.effects, async (updatedMove, effectData, deleteEffect) => {
|
||||||
|
let updatedEffects = null;
|
||||||
|
if (effectData) {
|
||||||
|
const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`);
|
||||||
|
const existingEffectIndex = currentEffects.findIndex(x => x.id === effectData.id);
|
||||||
|
|
||||||
|
updatedEffects = deleteEffect
|
||||||
|
? currentEffects.filter(x => x.id !== effectData.id)
|
||||||
|
: existingEffectIndex === -1
|
||||||
|
? [...currentEffects, effectData]
|
||||||
|
: currentEffects.with(existingEffectIndex, effectData);
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[`${this.movePath}.effects`]: updatedEffects
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove });
|
||||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
|
return updatedEffects;
|
||||||
}).render(true);
|
}).render(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static async removeItem(_, target) {
|
static async removeItem(_, target) {
|
||||||
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
const { type, id } = target.dataset;
|
||||||
|
if (type === 'effect') {
|
||||||
|
const move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
|
for (const action of move.actions) {
|
||||||
|
const remainingEffects = action.effects.filter(x => x._id !== id);
|
||||||
|
if (action.effects.length !== remainingEffects.length) {
|
||||||
|
await action.update({
|
||||||
|
effects: remainingEffects.map(x => {
|
||||||
|
const { _id, ...rest } = x;
|
||||||
|
return { ...rest, _id: _id };
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.settings.updateSource({
|
||||||
|
[this.movePath]: {
|
||||||
|
effects: move.effects.filter(x => x.id !== id),
|
||||||
|
actions: move.actions
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||||
|
}
|
||||||
|
|
||||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
114
module/applications/sheets-configs/token-config-mixin.mjs
Normal file
114
module/applications/sheets-configs/token-config-mixin.mjs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
export default function DHTokenConfigMixin(Base) {
|
||||||
|
class DHTokenConfigBase extends Base {
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
tabs: super.PARTS.tabs,
|
||||||
|
identity: super.PARTS.identity,
|
||||||
|
appearance: {
|
||||||
|
template: 'systems/daggerheart/templates/sheets-settings/token-config/appearance.hbs',
|
||||||
|
scrollable: ['']
|
||||||
|
},
|
||||||
|
vision: super.PARTS.vision,
|
||||||
|
light: super.PARTS.light,
|
||||||
|
resources: super.PARTS.resources,
|
||||||
|
footer: super.PARTS.footer
|
||||||
|
};
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
switch (partId) {
|
||||||
|
case 'appearance':
|
||||||
|
htmlElement
|
||||||
|
.querySelector('#dhTokenSize')
|
||||||
|
?.addEventListener('change', this.onTokenSizeChange.bind(this));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _prepareResourcesTab() {
|
||||||
|
const token = this.token;
|
||||||
|
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
||||||
|
const attributeSource =
|
||||||
|
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
||||||
|
? this.actor?.type
|
||||||
|
: this.actor?.system;
|
||||||
|
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
||||||
|
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
||||||
|
return {
|
||||||
|
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
||||||
|
bar1: token.getBarAttribute?.('bar1'),
|
||||||
|
bar2: token.getBarAttribute?.('bar2'),
|
||||||
|
turnMarkerModes: DHTokenConfigBase.TURN_MARKER_MODES,
|
||||||
|
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareAppearanceTab() {
|
||||||
|
const context = await super._prepareAppearanceTab();
|
||||||
|
context.tokenSizes = CONFIG.DH.ACTOR.tokenSize;
|
||||||
|
context.tokenSize = this.actor?.system?.size;
|
||||||
|
context.usesActorSize = this.actor?.system?.metadata?.usesSize;
|
||||||
|
context.actorSizeDisable = context.usesActorSize && this.actor.system.size !== 'custom';
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_previewChanges(changes) {
|
||||||
|
if (!changes || !this._preview) return;
|
||||||
|
|
||||||
|
const tokenSizeSelect = this.element?.querySelector('#dhTokenSize');
|
||||||
|
if (this.actor && tokenSizeSelect && tokenSizeSelect.value !== 'custom') {
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const tokenSize = tokenSizes[tokenSizeSelect.value];
|
||||||
|
changes.width = tokenSize;
|
||||||
|
changes.height = tokenSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletions = { '-=actorId': null, '-=actorLink': null };
|
||||||
|
const mergeOptions = { inplace: false, performDeletions: true };
|
||||||
|
this._preview.updateSource(mergeObject(changes, deletions, mergeOptions));
|
||||||
|
|
||||||
|
if (this._preview?.object?.destroyed === false) {
|
||||||
|
this._preview.object.initializeSources();
|
||||||
|
this._preview.object.renderFlags.set({ refresh: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onTokenSizeChange(event) {
|
||||||
|
const value = event.target.value;
|
||||||
|
const tokenSizeDimensions = this.element.querySelector('#tokenSizeDimensions');
|
||||||
|
if (tokenSizeDimensions) {
|
||||||
|
const disabled = value !== 'custom';
|
||||||
|
|
||||||
|
tokenSizeDimensions.dataset.tooltip = disabled
|
||||||
|
? game.i18n.localize('DAGGERHEART.APPLICATIONS.TokenConfig.actorSizeUsed')
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const disabledIcon = tokenSizeDimensions.querySelector('i');
|
||||||
|
if (disabledIcon) {
|
||||||
|
disabledIcon.style.opacity = disabled ? '' : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const dimensionsInputs = tokenSizeDimensions.querySelectorAll('.form-fields input');
|
||||||
|
for (const input of dimensionsInputs) {
|
||||||
|
input.disabled = disabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DHTokenConfigBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActorSizeFromForm(element, actor) {
|
||||||
|
const tokenSizeSelect = element.querySelector('#dhTokenSize');
|
||||||
|
const isSizeDifferent = tokenSizeSelect?.value !== actor?.system?.size;
|
||||||
|
if (tokenSizeSelect && actor && isSizeDifferent) {
|
||||||
|
return tokenSizeSelect.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,11 @@
|
||||||
export default class DhTokenConfig extends foundry.applications.sheets.TokenConfig {
|
import DHTokenConfigMixin from './token-config-mixin.mjs';
|
||||||
/** @inheritDoc */
|
import { getActorSizeFromForm } from './token-config-mixin.mjs';
|
||||||
async _prepareResourcesTab() {
|
|
||||||
const token = this.token;
|
export default class DhTokenConfig extends DHTokenConfigMixin(foundry.applications.sheets.TokenConfig) {
|
||||||
const usesTrackableAttributes = !foundry.utils.isEmpty(CONFIG.Actor.trackableAttributes);
|
async _processSubmitData(event, form, submitData, options) {
|
||||||
const attributeSource =
|
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
|
||||||
this.actor?.system instanceof foundry.abstract.DataModel && usesTrackableAttributes
|
if (changedTokenSizeValue) this.token.actor.update({ 'system.size': changedTokenSizeValue });
|
||||||
? this.actor?.type
|
|
||||||
: this.actor?.system;
|
super._processSubmitData(event, form, submitData, options);
|
||||||
const TokenDocument = foundry.utils.getDocumentClass('Token');
|
|
||||||
const attributes = TokenDocument.getTrackedAttributes(attributeSource);
|
|
||||||
return {
|
|
||||||
barAttributes: TokenDocument.getTrackedAttributeChoices(attributes, attributeSource),
|
|
||||||
bar1: token.getBarAttribute?.('bar1'),
|
|
||||||
bar2: token.getBarAttribute?.('bar2'),
|
|
||||||
turnMarkerModes: DhTokenConfig.TURN_MARKER_MODES,
|
|
||||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
action: 'editAttribution'
|
action: 'editAttribution'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
dragDrop: [
|
||||||
|
{
|
||||||
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
|
dropSelector: null
|
||||||
|
}
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
|
|
@ -87,6 +93,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
context.resources.stress.emptyPips =
|
context.resources.stress.emptyPips =
|
||||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||||
|
|
||||||
|
const featureForms = ['passive', 'action', 'reaction'];
|
||||||
|
context.features = this.document.system.features.sort((a, b) =>
|
||||||
|
a.system.featureForm !== b.system.featureForm
|
||||||
|
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||||
|
: a.sort - b.sort
|
||||||
|
);
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,6 +176,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
async _onDragStart(event) {
|
||||||
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
if (inventoryItem) {
|
||||||
|
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
|
||||||
|
}
|
||||||
|
super._onDragStart(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
useDowntime: this.useDowntime
|
useDowntime: this.useDowntime,
|
||||||
|
viewParty: CharacterSheet.#viewParty,
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -46,7 +47,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{
|
{
|
||||||
dragSelector: '[data-item-id][draggable="true"]',
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
dropSelector: null
|
dropSelector: null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -139,10 +140,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
element.addEventListener('change', this.updateItemResource.bind(this));
|
element.addEventListener('change', this.updateItemResource.bind(this));
|
||||||
element.addEventListener('click', e => e.stopPropagation());
|
element.addEventListener('click', e => e.stopPropagation());
|
||||||
});
|
});
|
||||||
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
|
||||||
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
|
||||||
element.addEventListener('click', e => e.stopPropagation());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add listener for armor marks input
|
// Add listener for armor marks input
|
||||||
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
||||||
|
|
@ -214,34 +211,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
context.resources.stress.emptyPips =
|
context.resources.stress.emptyPips =
|
||||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||||
|
|
||||||
context.inventory = { currencies: {} };
|
|
||||||
const { title, ...currencies } = game.settings.get(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Homebrew
|
|
||||||
).currency;
|
|
||||||
for (let key in currencies) {
|
|
||||||
context.inventory.currencies[key] = {
|
|
||||||
...currencies[key],
|
|
||||||
field: context.systemFields.gold.fields[key],
|
|
||||||
value: context.source.system.gold[key]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// context.inventory = {
|
|
||||||
// currency: {
|
|
||||||
// title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
|
|
||||||
// coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
|
|
||||||
// handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
|
|
||||||
// bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
|
|
||||||
// chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||||
|
|
||||||
// if (context.inventory.length === 0) {
|
|
||||||
// context.inventory = Array(1).fill(Array(5).fill([]));
|
|
||||||
// }
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,6 +319,40 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'recall',
|
||||||
|
icon: 'fa-solid fa-bolt-lightning',
|
||||||
|
condition: target => {
|
||||||
|
const doc = getDocFromElementSync(target);
|
||||||
|
return doc && doc.system.inVault;
|
||||||
|
},
|
||||||
|
callback: async (target, event) => {
|
||||||
|
const doc = await getDocFromElement(target);
|
||||||
|
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||||
|
if (!actorLoadout.available) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.loadoutMaxReached'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doc.system.recallCost == 0) {
|
||||||
|
return doc.update({ 'system.inVault': false });
|
||||||
|
}
|
||||||
|
const type = 'effect';
|
||||||
|
const cls = game.system.api.models.actions.actionsTypes[type];
|
||||||
|
const action = new cls({
|
||||||
|
...cls.getSourceConfig(doc.system),
|
||||||
|
type: type,
|
||||||
|
chatDisplay: false,
|
||||||
|
cost: [{
|
||||||
|
key: 'stress',
|
||||||
|
value: doc.system.recallCost
|
||||||
|
}]
|
||||||
|
}, { parent: doc.system });
|
||||||
|
const config = await action.use(event);
|
||||||
|
if (config) {
|
||||||
|
return doc.update({ 'system.inVault': false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'toVault',
|
name: 'toVault',
|
||||||
icon: 'fa-solid fa-arrow-down',
|
icon: 'fa-solid fa-arrow-down',
|
||||||
|
|
@ -619,14 +624,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateItemQuantity(event) {
|
|
||||||
const item = await getDocFromElement(event.currentTarget);
|
|
||||||
if (!item) return;
|
|
||||||
|
|
||||||
await item.update({ 'system.quantity': event.currentTarget.value });
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateArmorMarks(event) {
|
async updateArmorMarks(event) {
|
||||||
const armor = this.document.system.armor;
|
const armor = this.document.system.armor;
|
||||||
if (!armor) return;
|
if (!armor) return;
|
||||||
|
|
@ -713,17 +710,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
roll: {
|
roll: {
|
||||||
trait: button.dataset.attribute
|
trait: button.dataset.attribute
|
||||||
},
|
},
|
||||||
hasRoll: true
|
hasRoll: true,
|
||||||
};
|
actionType: 'action',
|
||||||
const result = await this.document.diceRoll({
|
|
||||||
...config,
|
|
||||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||||
ability: abilityLabel
|
ability: abilityLabel
|
||||||
})
|
})
|
||||||
});
|
};
|
||||||
|
const result = await this.document.diceRoll(config);
|
||||||
|
|
||||||
if (result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
|
/* This could be avoided by baking config.costs into config.resourceUpdates. Didn't feel like messing with it at the time */
|
||||||
|
const costResources = result.costs
|
||||||
|
.filter(x => x.enabled)
|
||||||
|
.map(cost => ({ ...cost, value: -cost.value, total: -cost.total }));
|
||||||
|
config.resourceUpdates.addResources(costResources);
|
||||||
|
await config.resourceUpdates.updateResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: redo toggleEquipItem method
|
//TODO: redo toggleEquipItem method
|
||||||
|
|
@ -892,6 +893,41 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
|
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #viewParty(_, target) {
|
||||||
|
const parties = this.document.parties;
|
||||||
|
if (parties.size <= 1) {
|
||||||
|
parties.first()?.sheet.render({ force: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons = parties.map((p) => {
|
||||||
|
const button = document.createElement("button");
|
||||||
|
button.type = "button";
|
||||||
|
button.classList.add("plain");
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = p.img;
|
||||||
|
button.append(img);
|
||||||
|
const name = document.createElement("span");
|
||||||
|
name.textContent = p.name;
|
||||||
|
button.append(name);
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
p.sheet?.render({ force: true });
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
});
|
||||||
|
return button;
|
||||||
|
});
|
||||||
|
|
||||||
|
const html = document.createElement("div");
|
||||||
|
html.classList.add("party-list");
|
||||||
|
html.append(...buttons);
|
||||||
|
|
||||||
|
game.tooltip.dismissLockedTooltips();
|
||||||
|
game.tooltip.activate(target, {
|
||||||
|
html,
|
||||||
|
locked: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open the downtime application.
|
* Open the downtime application.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
@ -902,47 +938,18 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const item = await getDocFromElement(event.target);
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
if (inventoryItem) {
|
||||||
const dragData = {
|
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
|
||||||
originActor: this.document.uuid,
|
}
|
||||||
originId: item.id,
|
|
||||||
type: item.documentName,
|
|
||||||
uuid: item.uuid
|
|
||||||
};
|
|
||||||
|
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
|
||||||
|
|
||||||
super._onDragStart(event);
|
super._onDragStart(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDropItem(event, item) {
|
||||||
// Prevent event bubbling to avoid duplicate handling
|
if (this.document.uuid === item.parent?.uuid) {
|
||||||
event.preventDefault();
|
return super._onDropItem(event, item);
|
||||||
event.stopPropagation();
|
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
|
||||||
|
|
||||||
const { cancel } = await super._onDrop(event);
|
|
||||||
if (cancel) return;
|
|
||||||
|
|
||||||
this._onDropItem(event, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onDropItem(event, data) {
|
|
||||||
const item = await Item.implementation.fromDropData(data);
|
|
||||||
const itemData = item.toObject();
|
|
||||||
|
|
||||||
if (item.type === 'domainCard' && !this.document.system.loadoutSlot.available) {
|
|
||||||
itemData.system.inVault = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typesThatReplace = ['ancestry', 'community'];
|
|
||||||
if (typesThatReplace.includes(item.type)) {
|
|
||||||
await this.document.deleteEmbeddedDocuments(
|
|
||||||
'Item',
|
|
||||||
this.document.items.filter(x => x.type === item.type).map(x => x.id)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === 'beastform') {
|
if (item.type === 'beastform') {
|
||||||
|
|
@ -952,20 +959,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const itemData = item.toObject();
|
||||||
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData);
|
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData);
|
||||||
if (data) {
|
if (!data?.selectedImage) {
|
||||||
if (!data.selectedImage) return;
|
return;
|
||||||
else {
|
} else if (data) {
|
||||||
if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage;
|
if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage;
|
||||||
else itemData.system.tokenImg = data.selectedImage;
|
else itemData.system.tokenImg = data.selectedImage;
|
||||||
}
|
return await this._onDropItemCreate(itemData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData);
|
// If this is a type that gets deleted, delete it first (but still defer to super)
|
||||||
const createdItem = await this._onDropItemCreate(itemData);
|
const typesThatReplace = ['ancestry', 'community'];
|
||||||
|
if (typesThatReplace.includes(item.type)) {
|
||||||
|
await this.document.deleteEmbeddedDocuments(
|
||||||
|
'Item',
|
||||||
|
this.document.items.filter(x => x.type === item.type).map(x => x.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return createdItem;
|
return super._onDropItem(event, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onDropItemCreate(itemData, event) {
|
async _onDropItemCreate(itemData, event) {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,12 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
toggleResourceDice: DhpEnvironment.#toggleResourceDice,
|
toggleResourceDice: DhpEnvironment.#toggleResourceDice,
|
||||||
handleResourceDice: DhpEnvironment.#handleResourceDice
|
handleResourceDice: DhpEnvironment.#handleResourceDice
|
||||||
},
|
},
|
||||||
dragDrop: [{ dragSelector: '.inventory-item', dropSelector: null }]
|
dragDrop: [
|
||||||
|
{
|
||||||
|
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||||
|
dropSelector: null
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -37,11 +42,11 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
|
||||||
features: {
|
features: {
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs',
|
template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs',
|
||||||
scrollable: ['feature-section']
|
scrollable: ['.feature-section']
|
||||||
},
|
},
|
||||||
potentialAdversaries: {
|
potentialAdversaries: {
|
||||||
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs',
|
template: 'systems/daggerheart/templates/sheets/actors/environment/potentialAdversaries.hbs',
|
||||||
scrollable: ['items-sections']
|
scrollable: ['.items-section']
|
||||||
},
|
},
|
||||||
notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
|
notes: { template: 'systems/daggerheart/templates/sheets/actors/environment/notes.hbs' }
|
||||||
};
|
};
|
||||||
|
|
@ -74,6 +79,9 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
case 'header':
|
case 'header':
|
||||||
await this._prepareHeaderContext(context, options);
|
await this._prepareHeaderContext(context, options);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'features':
|
||||||
|
await this._prepareFeaturesContext(context, options);
|
||||||
break;
|
break;
|
||||||
case 'notes':
|
case 'notes':
|
||||||
await this._prepareNotesContext(context, options);
|
await this._prepareNotesContext(context, options);
|
||||||
|
|
@ -110,6 +118,22 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare render context for the features part.
|
||||||
|
* @param {ApplicationRenderContext} context
|
||||||
|
* @param {ApplicationRenderOptions} options
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
async _prepareFeaturesContext(context, _options) {
|
||||||
|
const featureForms = ['passive', 'action', 'reaction'];
|
||||||
|
context.features = this.document.system.features.sort((a, b) =>
|
||||||
|
a.system.featureForm !== b.system.featureForm
|
||||||
|
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||||
|
: a.sort - b.sort
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare render context for the Header part.
|
* Prepare render context for the Header part.
|
||||||
* @param {ApplicationRenderContext} context
|
* @param {ApplicationRenderContext} context
|
||||||
|
|
@ -130,12 +154,13 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const item = event.currentTarget.closest('.inventory-item');
|
const item = event.currentTarget.closest('.inventory-item[data-type=adversary]');
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
||||||
event.dataTransfer.setDragImage(item, 60, 0);
|
event.dataTransfer.setDragImage(item, 60, 0);
|
||||||
|
} else {
|
||||||
|
return super._onDragStart(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||||
refreshActors: DaggerheartMenu.refreshActors
|
refreshActors: DaggerheartMenu.refreshActors
|
||||||
},
|
},
|
||||||
dragDrop: [{ dragSelector: '[data-item-id][draggable="true"]', dropSelector: null }]
|
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -93,25 +93,6 @@ export default class Party extends DHBaseActorSheet {
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
|
|
||||||
context.inventory = { currencies: {} };
|
|
||||||
const { title, ...currencies } = game.settings.get(
|
|
||||||
CONFIG.DH.id,
|
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Homebrew
|
|
||||||
).currency;
|
|
||||||
for (let key in currencies) {
|
|
||||||
context.inventory.currencies[key] = {
|
|
||||||
...currencies[key],
|
|
||||||
field: context.systemFields.gold.fields[key],
|
|
||||||
value: context.source.system.gold[key]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _preparePartContext(partId, context, options) {
|
async _preparePartContext(partId, context, options) {
|
||||||
context = await super._preparePartContext(partId, context, options);
|
context = await super._preparePartContext(partId, context, options);
|
||||||
switch (partId) {
|
switch (partId) {
|
||||||
|
|
@ -438,30 +419,9 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
async _onDragStart(event) {
|
|
||||||
const item = await getDocFromElement(event.target);
|
|
||||||
const dragData = {
|
|
||||||
originActor: this.document.uuid,
|
|
||||||
originId: item.id,
|
|
||||||
type: item.documentName,
|
|
||||||
uuid: item.uuid
|
|
||||||
};
|
|
||||||
|
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
async _onDropActor(event, document) {
|
||||||
super._onDragStart(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onDrop(event) {
|
|
||||||
// Prevent event bubbling to avoid duplicate handling
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
|
||||||
const { cancel } = await super._onDrop(event);
|
|
||||||
if (cancel) return;
|
|
||||||
|
|
||||||
const document = await foundry.utils.fromUuid(data.uuid);
|
|
||||||
|
|
||||||
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
if (document instanceof DhpActor && Party.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)) {
|
||||||
|
|
@ -469,11 +429,11 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
|
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
|
||||||
} else if (document instanceof DHItem) {
|
|
||||||
this.document.createEmbeddedDocuments('Item', [document.toObject()]);
|
|
||||||
} else {
|
} else {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #deletePartyMember(event, target) {
|
static async #deletePartyMember(event, target) {
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,79 @@ export default function DHApplicationMixin(Base) {
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
this._dragDrop.forEach(d => d.bind(htmlElement));
|
this._dragDrop.forEach(d => d.bind(htmlElement));
|
||||||
|
|
||||||
|
// Handle delta inputs
|
||||||
|
for (const deltaInput of htmlElement.querySelectorAll('input[data-allow-delta]')) {
|
||||||
|
deltaInput.dataset.numValue = deltaInput.value;
|
||||||
|
deltaInput.inputMode = 'numeric';
|
||||||
|
|
||||||
|
const handleUpdate = (delta = 0) => {
|
||||||
|
const min = Number(deltaInput.min) || 0;
|
||||||
|
const max = Number(deltaInput.max) || Infinity;
|
||||||
|
const current = Number(deltaInput.dataset.numValue);
|
||||||
|
const rawNumber = Number(deltaInput.value);
|
||||||
|
if (Number.isNaN(rawNumber)) {
|
||||||
|
deltaInput.value = delta ? Math.clamp(current + delta, min, max) : current;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue =
|
||||||
|
deltaInput.value.startsWith('+') || deltaInput.value.startsWith('-')
|
||||||
|
? Math.clamp(current + rawNumber + delta, min, max)
|
||||||
|
: Math.clamp(rawNumber + delta, min, max);
|
||||||
|
deltaInput.value = deltaInput.dataset.numValue = newValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Force valid characters while inputting
|
||||||
|
deltaInput.addEventListener('input', () => {
|
||||||
|
deltaInput.value = /[+=\-]?\d*/.exec(deltaInput.value)?.at(0) ?? deltaInput.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recreate Keyup/Keydown support
|
||||||
|
deltaInput.addEventListener('keydown', event => {
|
||||||
|
const step = event.key === 'ArrowUp' ? 1 : event.key === 'ArrowDown' ? -1 : 0;
|
||||||
|
if (step !== 0) {
|
||||||
|
handleUpdate(step);
|
||||||
|
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mousewheel while focused support
|
||||||
|
deltaInput.addEventListener(
|
||||||
|
'wheel',
|
||||||
|
event => {
|
||||||
|
if (deltaInput === document.activeElement) {
|
||||||
|
event.preventDefault();
|
||||||
|
handleUpdate(Math.sign(-1 * event.deltaY));
|
||||||
|
deltaInput.dispatchEvent(new Event("change", { bubbles: true }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ passive: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
deltaInput.addEventListener('change', () => {
|
||||||
|
handleUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle contenteditable
|
||||||
|
for (const input of htmlElement.querySelectorAll('[contenteditable][data-property]')) {
|
||||||
|
const property = input.dataset.property;
|
||||||
|
input.addEventListener("blur", () => {
|
||||||
|
const selection = document.getSelection();
|
||||||
|
if (input.contains(selection.anchorNode)) {
|
||||||
|
selection.empty();
|
||||||
|
}
|
||||||
|
this.document.update({ [property]: input.textContent });
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener("keydown", event => {
|
||||||
|
if (event.key === "Enter") input.blur();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chrome sometimes add <br>, which aren't a problem for the value but are for the placeholder
|
||||||
|
input.addEventListener("input", () => input.querySelectorAll("br").forEach((i) => i.remove()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -322,10 +395,11 @@ export default function DHApplicationMixin(Base) {
|
||||||
_onDrop(event) {
|
_onDrop(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.fromInternal === this.document.uuid) return;
|
if (data.type === 'ActiveEffect' && data.fromInternal !== this.document.uuid) {
|
||||||
|
|
||||||
if (data.type === 'ActiveEffect') {
|
|
||||||
this.document.createEmbeddedDocuments('ActiveEffect', [data.data]);
|
this.document.createEmbeddedDocuments('ActiveEffect', [data.data]);
|
||||||
|
} else {
|
||||||
|
// Fallback to super, but note that item sheets do not have this function
|
||||||
|
return super._onDrop?.(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -662,6 +736,9 @@ export default function DHApplicationMixin(Base) {
|
||||||
};
|
};
|
||||||
if (inVault) data['system.inVault'] = true;
|
if (inVault) data['system.inVault'] = true;
|
||||||
if (disabled) data.disabled = true;
|
if (disabled) data.disabled = true;
|
||||||
|
if (type === "domainCard" && parent?.system.domains?.length) {
|
||||||
|
data.system.domain = parent.system.domains[0];
|
||||||
|
}
|
||||||
|
|
||||||
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||||
if (parentIsItem && type === 'feature') {
|
if (parentIsItem && type === 'feature') {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { itemIsIdentical } from '../../../helpers/utils.mjs';
|
import { getDocFromElement, itemIsIdentical } from '../../../helpers/utils.mjs';
|
||||||
import DHBaseActorSettings from './actor-setting.mjs';
|
import DHBaseActorSettings from './actor-setting.mjs';
|
||||||
import DHApplicationMixin from './application-mixin.mjs';
|
import DHApplicationMixin from './application-mixin.mjs';
|
||||||
|
|
||||||
|
|
@ -34,7 +34,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
|
dragDrop: [
|
||||||
|
{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null },
|
||||||
|
{ dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -69,6 +72,29 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||||
.hideAttribution;
|
.hideAttribution;
|
||||||
|
|
||||||
|
// Prepare inventory data
|
||||||
|
if (['party', 'character'].includes(this.document.type)) {
|
||||||
|
context.inventory = {
|
||||||
|
currencies: {},
|
||||||
|
weapons: this.document.itemTypes.weapon.sort((a, b) => a.sort - b.sort),
|
||||||
|
armor: this.document.itemTypes.armor.sort((a, b) => a.sort - b.sort),
|
||||||
|
consumables: this.document.itemTypes.consumable.sort((a, b) => a.sort - b.sort),
|
||||||
|
loot: this.document.itemTypes.loot.sort((a, b) => a.sort - b.sort)
|
||||||
|
};
|
||||||
|
const { title, ...currencies } = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.Homebrew
|
||||||
|
).currency;
|
||||||
|
for (const key in currencies) {
|
||||||
|
context.inventory.currencies[key] = {
|
||||||
|
...currencies[key],
|
||||||
|
field: context.systemFields.gold.fields[key],
|
||||||
|
value: context.source.system.gold[key]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,6 +138,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
_attachPartListeners(partId, htmlElement, options) {
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
super._attachPartListeners(partId, htmlElement, options);
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.inventory-item-quantity').forEach(element => {
|
||||||
|
element.addEventListener('change', this.updateItemQuantity.bind(this));
|
||||||
|
element.addEventListener('click', e => e.stopPropagation());
|
||||||
|
});
|
||||||
htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => {
|
htmlElement.querySelectorAll('.item-button .action-uses-button').forEach(element => {
|
||||||
element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses);
|
element.addEventListener('contextmenu', DHBaseActorSheet.#modifyActionUses);
|
||||||
});
|
});
|
||||||
|
|
@ -150,6 +180,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Application Listener Actions */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async updateItemQuantity(event) {
|
||||||
|
const item = await getDocFromElement(event.currentTarget);
|
||||||
|
await item?.update({ 'system.quantity': event.currentTarget.value });
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Application Clicks Actions */
|
/* Application Clicks Actions */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -219,67 +258,79 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.originActor === this.document.uuid) return { cancel: true };
|
if (data.type === 'Currency' && ['character', 'party'].includes(this.document.type)) {
|
||||||
|
const originActor = await foundry.utils.fromUuid(data.originActor);
|
||||||
/* Handling transfer of inventoryItems */
|
if (!originActor || originActor.uuid === this.document.uuid) return;
|
||||||
let cancel = false;
|
const currency = data.currency;
|
||||||
const physicalActorTypes = ['character', 'party'];
|
const quantity = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
if (physicalActorTypes.includes(this.document.type)) {
|
originActor,
|
||||||
const originActor = data.originActor ? await foundry.utils.fromUuid(data.originActor) : null;
|
targetActor: this.document,
|
||||||
if (data.originId && originActor && physicalActorTypes.includes(originActor.type)) {
|
currency
|
||||||
const dropDocument = await foundry.utils.fromUuid(data.uuid);
|
});
|
||||||
|
if (quantity) {
|
||||||
if (dropDocument.system.metadata.isInventoryItem) {
|
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
|
||||||
cancel = true;
|
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
|
||||||
if (dropDocument.system.metadata.isQuantifiable) {
|
|
||||||
const actorItem = originActor.items.get(data.originId);
|
|
||||||
const quantityTransfered =
|
|
||||||
actorItem.system.quantity === 1
|
|
||||||
? 1
|
|
||||||
: await game.system.api.applications.dialogs.ItemTransferDialog.configure(dropDocument);
|
|
||||||
|
|
||||||
if (quantityTransfered) {
|
|
||||||
if (quantityTransfered === actorItem.system.quantity) {
|
|
||||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
|
||||||
} else {
|
|
||||||
cancel = true;
|
|
||||||
await actorItem.update({
|
|
||||||
'system.quantity': actorItem.system.quantity - quantityTransfered
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingItem = this.document.items.find(x => itemIsIdentical(x, dropDocument));
|
|
||||||
if (existingItem) {
|
|
||||||
cancel = true;
|
|
||||||
await existingItem.update({
|
|
||||||
'system.quantity': existingItem.system.quantity + quantityTransfered
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const createData = dropDocument.toObject();
|
|
||||||
await this.document.createEmbeddedDocuments('Item', [
|
|
||||||
{
|
|
||||||
...createData,
|
|
||||||
system: {
|
|
||||||
...createData.system,
|
|
||||||
quantity: quantityTransfered
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cancel = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await originActor.deleteEmbeddedDocuments('Item', [data.originId], { render: false });
|
|
||||||
const createData = dropDocument.toObject();
|
|
||||||
await this.document.createEmbeddedDocuments('Item', [createData]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { cancel };
|
return super._onDrop(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onDropItem(event, item) {
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
const originActor = item.actor;
|
||||||
|
if (
|
||||||
|
item.actor?.uuid === this.document.uuid ||
|
||||||
|
!originActor ||
|
||||||
|
!['character', 'party'].includes(this.document.type)
|
||||||
|
) {
|
||||||
|
return super._onDropItem(event, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handling transfer of inventoryItems */
|
||||||
|
if (item.system.metadata.isInventoryItem) {
|
||||||
|
if (item.system.metadata.isQuantifiable) {
|
||||||
|
const actorItem = originActor.items.get(data.originId);
|
||||||
|
const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
|
item,
|
||||||
|
targetActor: this.document
|
||||||
|
});
|
||||||
|
|
||||||
|
if (quantityTransfered) {
|
||||||
|
if (quantityTransfered === actorItem.system.quantity) {
|
||||||
|
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||||
|
} else {
|
||||||
|
await actorItem.update({
|
||||||
|
'system.quantity': actorItem.system.quantity - quantityTransfered
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingItem = this.document.items.find(x => itemIsIdentical(x, item));
|
||||||
|
if (existingItem) {
|
||||||
|
await existingItem.update({
|
||||||
|
'system.quantity': existingItem.system.quantity + quantityTransfered
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const createData = item.toObject();
|
||||||
|
await this.document.createEmbeddedDocuments('Item', [
|
||||||
|
{
|
||||||
|
...createData,
|
||||||
|
system: {
|
||||||
|
...createData.system,
|
||||||
|
quantity: quantityTransfered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await originActor.deleteEmbeddedDocuments('Item', [data.originId]);
|
||||||
|
await this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -287,8 +338,17 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
*/
|
*/
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
|
// Handle drag/dropping currencies
|
||||||
|
const currencyEl = event.currentTarget.closest(".currency[data-currency]");
|
||||||
|
if (currencyEl) {
|
||||||
|
const currency = currencyEl.dataset.currency;
|
||||||
|
const data = { type: 'Currency', currency, originActor: this.document.uuid };
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(data));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle drag/dropping attacks
|
||||||
|
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
|
||||||
if (attackItem) {
|
if (attackItem) {
|
||||||
const attackData = {
|
const attackData = {
|
||||||
type: 'Attack',
|
type: 'Attack',
|
||||||
|
|
@ -298,8 +358,20 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
};
|
};
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
||||||
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
||||||
} else if (this.document.type !== 'environment') {
|
return;
|
||||||
super._onDragStart(event);
|
}
|
||||||
|
|
||||||
|
const item = await getDocFromElement(event.target);
|
||||||
|
if (item) {
|
||||||
|
const dragData = {
|
||||||
|
originActor: this.document.uuid,
|
||||||
|
originId: item.id,
|
||||||
|
type: item.documentName,
|
||||||
|
uuid: item.uuid
|
||||||
|
};
|
||||||
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super._onDragStart(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
||||||
name: context.document.system.advantageOn[key].value
|
name: context.document.system.advantageOn[key].value
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
context.dimensionsDisabled = context.document.system.tokenSize.size !== 'custom';
|
||||||
break;
|
break;
|
||||||
case 'effects':
|
case 'effects':
|
||||||
context.effects.actives = context.effects.actives.map(effect => {
|
context.effects.actives = context.effects.actives.map(effect => {
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,11 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
||||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
//Might be wrong location but testing out if here is okay.
|
||||||
|
/**@override */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.featureFormChoices = CONFIG.DH.ITEM.featureForm;
|
||||||
|
return context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
|
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
|
||||||
|
export { default as DhActorDirectory } from './tabs/actorDirectory.mjs';
|
||||||
export { default as DhSidebar } from './sidebar.mjs';
|
export { default as DhSidebar } from './sidebar.mjs';
|
||||||
|
|
|
||||||
46
module/applications/sidebar/tabs/actorDirectory.mjs
Normal file
46
module/applications/sidebar/tabs/actorDirectory.mjs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
export default class DhActorDirectory extends foundry.applications.sidebar.tabs.ActorDirectory {
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
renderUpdateKeys: ['system.levelData.level.current', 'system.partner', 'system.tier']
|
||||||
|
};
|
||||||
|
|
||||||
|
static _entryPartial = 'systems/daggerheart/templates/ui/sidebar/actor-document-partial.hbs';
|
||||||
|
|
||||||
|
async _prepareDirectoryContext(context, options) {
|
||||||
|
await super._prepareDirectoryContext(context, options);
|
||||||
|
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
|
||||||
|
const environmentTypes = CONFIG.DH.ACTOR.environmentTypes;
|
||||||
|
context.getTypeLabel = document => {
|
||||||
|
return document.type === 'adversary'
|
||||||
|
? game.i18n.localize(adversaryTypes[document.system.type]?.label ?? 'TYPES.Actor.adversary')
|
||||||
|
: document.type === 'environment'
|
||||||
|
? game.i18n.localize(environmentTypes[document.system.type]?.label ?? 'TYPES.Actor.environment')
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_onDragStart(event) {
|
||||||
|
let actor;
|
||||||
|
const { entryId } = event.currentTarget.dataset;
|
||||||
|
if (entryId) {
|
||||||
|
actor = this.collection.get(entryId);
|
||||||
|
if (!actor?.visible) return false;
|
||||||
|
}
|
||||||
|
super._onDragStart(event);
|
||||||
|
|
||||||
|
// Create the drag preview.
|
||||||
|
if (actor && canvas.ready) {
|
||||||
|
const img = event.currentTarget.querySelector('img');
|
||||||
|
const pt = actor.prototypeToken;
|
||||||
|
const usesSize = actor.system.metadata.usesSize;
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const width = usesSize ? tokenSizes[actor.system.size] : pt.width;
|
||||||
|
const height = usesSize ? tokenSizes[actor.system.size] : pt.height;
|
||||||
|
|
||||||
|
const w = width * canvas.dimensions.size * Math.abs(pt.texture.scaleX) * canvas.stage.scale.x;
|
||||||
|
const h = height * canvas.dimensions.size * Math.abs(pt.texture.scaleY) * canvas.stage.scale.y;
|
||||||
|
const preview = foundry.applications.ux.DragDrop.implementation.createDragImage(img, w, h);
|
||||||
|
event.dataTransfer.setDragImage(preview, w / 2, h / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { refreshIsAllowed } from '../../../helpers/utils.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
const { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||||
/**
|
/**
|
||||||
|
|
@ -58,7 +60,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||||
const updates = {};
|
const updates = {};
|
||||||
for (let item of actor.items) {
|
for (let item of actor.items) {
|
||||||
if (item.system.metadata?.hasResource && types.includes(item.system.resource?.recovery)) {
|
if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) {
|
||||||
if (!refreshedActors[actor.id])
|
if (!refreshedActors[actor.id])
|
||||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||||
refreshedActors[actor.id].refreshed.add(
|
refreshedActors[actor.id].refreshed.add(
|
||||||
|
|
@ -79,7 +81,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
if (item.system.metadata?.hasActions) {
|
if (item.system.metadata?.hasActions) {
|
||||||
const refreshTypes = new Set();
|
const refreshTypes = new Set();
|
||||||
const actions = item.system.actions.filter(action => {
|
const actions = item.system.actions.filter(action => {
|
||||||
if (types.includes(action.uses.recovery)) {
|
if (refreshIsAllowed(types, action.uses.recovery)) {
|
||||||
refreshTypes.add(action.uses.recovery);
|
refreshTypes.add(action.uses.recovery);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ export { default as CountdownEdit } from './countdownEdit.mjs';
|
||||||
export { default as DhCountdowns } from './countdowns.mjs';
|
export { default as DhCountdowns } from './countdowns.mjs';
|
||||||
export { default as DhChatLog } from './chatLog.mjs';
|
export { default as DhChatLog } from './chatLog.mjs';
|
||||||
export { default as DhCombatTracker } from './combatTracker.mjs';
|
export { default as DhCombatTracker } from './combatTracker.mjs';
|
||||||
|
export { default as DhEffectsDisplay } from './effectsDisplay.mjs';
|
||||||
export { default as DhFearTracker } from './fearTracker.mjs';
|
export { default as DhFearTracker } from './fearTracker.mjs';
|
||||||
export { default as DhHotbar } from './hotbar.mjs';
|
export { default as DhHotbar } from './hotbar.mjs';
|
||||||
export { ItemBrowser } from './itemBrowser.mjs';
|
export { ItemBrowser } from './itemBrowser.mjs';
|
||||||
|
|
|
||||||
|
|
@ -55,27 +55,28 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
addChatListeners = async (app, html, data) => {
|
addChatListeners = async (document, html, data) => {
|
||||||
|
const message = data?.message ?? document.toObject(false);
|
||||||
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.onRollSimple(event, data.message))
|
element.addEventListener('click', event => this.onRollSimple(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.ability-use-button').forEach(element =>
|
html.querySelectorAll('.ability-use-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
|
element.addEventListener('click', event => this.abilityUseButton(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.action-use-button').forEach(element =>
|
html.querySelectorAll('.action-use-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.actionUseButton(event, data.message))
|
element.addEventListener('click', event => this.actionUseButton(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.reroll-button').forEach(element =>
|
html.querySelectorAll('.reroll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.rerollEvent(event, data.message))
|
element.addEventListener('click', event => this.rerollEvent(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.group-roll-button').forEach(element =>
|
html.querySelectorAll('.group-roll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.groupRollButton(event, data.message))
|
element.addEventListener('click', event => this.groupRollButton(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.group-roll-reroll').forEach(element =>
|
html.querySelectorAll('.group-roll-reroll').forEach(element =>
|
||||||
element.addEventListener('click', event => this.groupRollReroll(event, data.message))
|
element.addEventListener('click', event => this.groupRollReroll(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.group-roll-success').forEach(element =>
|
html.querySelectorAll('.group-roll-success').forEach(element =>
|
||||||
element.addEventListener('click', event => this.groupRollSuccessEvent(event, data.message))
|
element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
|
||||||
);
|
);
|
||||||
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
|
||||||
element.addEventListener('click', this.groupRollExpandSection)
|
element.addEventListener('click', this.groupRollExpandSection)
|
||||||
|
|
@ -133,7 +134,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
|
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
|
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
|
||||||
const parent = await foundry.utils.fromUuid(message.system.actor);
|
const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
|
||||||
|
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
|
||||||
|
|
||||||
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
const actionType = message.system.moves[moveIndex].actions[actionIndex];
|
||||||
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
||||||
const action = new cls(
|
const action = new cls(
|
||||||
|
|
@ -145,7 +148,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
type: CONFIG.DH.ITEM.originItemType.restMove,
|
type: CONFIG.DH.ITEM.originItemType.restMove,
|
||||||
itemPath: movePath,
|
itemPath: movePath,
|
||||||
actionIndex: actionIndex
|
actionIndex: actionIndex
|
||||||
}
|
},
|
||||||
|
targetUuid: targetUuid
|
||||||
},
|
},
|
||||||
{ parent: parent.system }
|
{ parent: parent.system }
|
||||||
);
|
);
|
||||||
|
|
@ -245,7 +249,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
await game.system.api.fields.ActionFields.CostField.execute.call({ actor }, result);
|
|
||||||
|
|
||||||
const newMessageData = foundry.utils.deepClone(message.system);
|
const newMessageData = foundry.utils.deepClone(message.system);
|
||||||
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
|
||||||
|
|
||||||
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
actions: {
|
actions: {
|
||||||
requestSpotlight: this.requestSpotlight,
|
requestSpotlight: this.requestSpotlight,
|
||||||
toggleSpotlight: this.toggleSpotlight,
|
toggleSpotlight: this.toggleSpotlight,
|
||||||
setActionTokens: this.setActionTokens,
|
setActionTokens: this.setActionTokens
|
||||||
openCountdowns: this.openCountdowns
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -20,11 +21,33 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _preparePartContext(_partId, context, _options) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
|
||||||
|
await this._prepareTrackerContext(context, options);
|
||||||
|
await this._prepareCombatContext(context, options);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
async _prepareCombatContext(context, options) {
|
async _prepareCombatContext(context, options) {
|
||||||
await super._prepareCombatContext(context, options);
|
await super._prepareCombatContext(context, options);
|
||||||
|
|
||||||
|
const modifierBP =
|
||||||
|
this.combats
|
||||||
|
.find(x => x.active)
|
||||||
|
?.system?.extendedBattleToggles?.reduce((acc, toggle) => (acc ?? 0) + toggle.category, null) ?? null;
|
||||||
|
const maxBP = CONFIG.DH.ENCOUNTER.BaseBPPerEncounter(context.characters.length) + modifierBP;
|
||||||
|
const currentBP = AdversaryBPPerEncounter(context.adversaries, context.characters);
|
||||||
|
|
||||||
Object.assign(context, {
|
Object.assign(context, {
|
||||||
fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
fear: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||||
|
battlepoints: { max: maxBP, current: currentBP, hasModifierBP: modifierBP !== null }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,21 +56,26 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
|
|
||||||
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
||||||
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||||
|
const spotlightQueueEnabled = game.settings.get(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue
|
||||||
|
);
|
||||||
|
|
||||||
const spotlightRequests = characters
|
const spotlightRequests = characters
|
||||||
?.filter(x => !x.isNPC)
|
?.filter(x => !x.isNPC && spotlightQueueEnabled)
|
||||||
.filter(x => x.system.spotlight.requestOrderIndex > 0)
|
.filter(x => x.system.spotlight.requestOrderIndex > 0)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const valueA = a.system.spotlight.requestOrderIndex;
|
const valueA = a.system.spotlight.requestOrderIndex;
|
||||||
const valueB = b.system.spotlight.requestOrderIndex;
|
const valueB = b.system.spotlight.requestOrderIndex;
|
||||||
|
|
||||||
return valueA - valueB;
|
return valueA - valueB;
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(context, {
|
Object.assign(context, {
|
||||||
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
||||||
adversaries,
|
adversaries,
|
||||||
characters: characters?.filter(x => !x.isNPC).filter(x => x.system.spotlight.requestOrderIndex == 0),
|
characters: characters
|
||||||
|
?.filter(x => !x.isNPC)
|
||||||
|
.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
|
||||||
spotlightRequests
|
spotlightRequests
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
resource,
|
resource,
|
||||||
active: index === combat.turn,
|
active: index === combat.turn,
|
||||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||||
|
type: combatant.actor?.system?.type,
|
||||||
img: await this._getCombatantThumbnail(combatant)
|
img: await this._getCombatantThumbnail(combatant)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -136,9 +165,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
||||||
|
|
||||||
if (this.viewed.turn !== toggleTurn) {
|
if (this.viewed.turn !== toggleTurn) {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id);
|
if (combatant.actor?.type === 'character') {
|
||||||
if (combatant.actor.type === 'character') {
|
await updateCountdowns(
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id);
|
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
|
const autoPoints = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).actionPoints;
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.sidebarCollapsed = true;
|
|
||||||
this.setupHooks();
|
this.setupHooks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,11 +97,10 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const context = await super._prepareContext(options);
|
const context = await super._prepareContext(options);
|
||||||
context.isGM = game.user.isGM;
|
context.isGM = game.user.isGM;
|
||||||
context.sidebarCollapsed = this.sidebarCollapsed;
|
|
||||||
context.iconOnly =
|
context.iconOnly =
|
||||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
||||||
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
||||||
|
|
||||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
||||||
const playersWithAccess = game.users.reduce((acc, user) => {
|
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||||
|
|
@ -247,14 +245,20 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return super.close(options);
|
return super.close(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async updateCountdowns(progressType) {
|
/**
|
||||||
|
* Sends updates of the countdowns to the GM player. Since this is asynchronous, be sure to
|
||||||
|
* update all the countdowns at the same time.
|
||||||
|
*
|
||||||
|
* @param {...any} progressTypes Countdowns to be updated
|
||||||
|
*/
|
||||||
|
static async updateCountdowns(...progressTypes) {
|
||||||
const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
const { countdownAutomation } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
if (!countdownAutomation) return;
|
if (!countdownAutomation) return;
|
||||||
|
|
||||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => {
|
const updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, key) => {
|
||||||
const countdown = countdownSetting.countdowns[key];
|
const countdown = countdownSetting.countdowns[key];
|
||||||
if (countdown.progress.type === progressType && countdown.progress.current > 0) {
|
if (progressTypes.indexOf(countdown.progress.type) !== -1 && countdown.progress.current > 0) {
|
||||||
acc.push(key);
|
acc.push(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,7 +266,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const countdownData = countdownSetting.toObject();
|
const countdownData = countdownSetting.toObject();
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, {
|
const settings = {
|
||||||
...countdownData,
|
...countdownData,
|
||||||
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
||||||
const countdown = foundry.utils.deepClone(countdownData.countdowns[key]);
|
const countdown = foundry.utils.deepClone(countdownData.countdowns[key]);
|
||||||
|
|
@ -273,14 +277,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
acc[key] = countdown;
|
acc[key] = countdown;
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
|
};
|
||||||
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
|
||||||
|
DhCountdowns.gmSetSetting.bind(settings),
|
||||||
|
settings, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = { refreshType: RefreshType.Countdown };
|
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
|
||||||
action: socketEvent.Refresh,
|
|
||||||
data
|
|
||||||
});
|
|
||||||
Hooks.callAll(socketEvent.Refresh, data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
|
|
|
||||||
117
module/applications/ui/effectsDisplay.mjs
Normal file
117
module/applications/ui/effectsDisplay.mjs
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A UI element which displays the Active Effects on a selected token.
|
||||||
|
*
|
||||||
|
* @extends ApplicationV2
|
||||||
|
* @mixes HandlebarsApplication
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class DhEffectsDisplay extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
this.setupHooks();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
id: 'effects-display',
|
||||||
|
tag: 'div',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'effects-display'],
|
||||||
|
window: {
|
||||||
|
frame: false,
|
||||||
|
positioned: false,
|
||||||
|
resizable: false,
|
||||||
|
minimizable: false
|
||||||
|
},
|
||||||
|
actions: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static PARTS = {
|
||||||
|
resources: {
|
||||||
|
root: true,
|
||||||
|
template: 'systems/daggerheart/templates/ui/effects-display.hbs'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
get element() {
|
||||||
|
return document.body.querySelector('.daggerheart.dh-style.effects-display');
|
||||||
|
}
|
||||||
|
|
||||||
|
get hidden() {
|
||||||
|
return this.element.classList.contains('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachPartListeners(partId, htmlElement, options) {
|
||||||
|
super._attachPartListeners(partId, htmlElement, options);
|
||||||
|
|
||||||
|
if (this.element) {
|
||||||
|
this.element.querySelectorAll('.effect-container a').forEach(element => {
|
||||||
|
element.addEventListener('contextmenu', this.removeEffect.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
context.effects = DhEffectsDisplay.getTokenEffects();
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTokenEffects = token => {
|
||||||
|
const actor = token
|
||||||
|
? token.actor
|
||||||
|
: canvas.tokens.controlled.length === 0
|
||||||
|
? !game.user.isGM
|
||||||
|
? game.user.character
|
||||||
|
: null
|
||||||
|
: canvas.tokens.controlled[0].actor;
|
||||||
|
return actor?.getActiveEffects() ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleHidden(token, focused) {
|
||||||
|
const effects = DhEffectsDisplay.getTokenEffects(focused ? token : null);
|
||||||
|
this.element.hidden = effects.length === 0;
|
||||||
|
|
||||||
|
Hooks.callAll(CONFIG.DH.HOOKS.effectDisplayToggle, this.element.hidden, token);
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeEffect(event) {
|
||||||
|
const element = event.target.closest('.effect-container');
|
||||||
|
const effects = DhEffectsDisplay.getTokenEffects();
|
||||||
|
const effect = effects.find(x => x.id === element.dataset.effectId);
|
||||||
|
await effect.delete();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHooks() {
|
||||||
|
Hooks.on('controlToken', this.toggleHidden.bind(this));
|
||||||
|
Hooks.on(RefreshType.EffectsDisplay, this.toggleHidden.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async close(options) {
|
||||||
|
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||||
|
if (options.closeKey) return;
|
||||||
|
|
||||||
|
Hooks.off('controlToken', this.toggleHidden);
|
||||||
|
Hooks.off(RefreshType.EffectsDisplay, this.toggleHidden);
|
||||||
|
return super.close(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
this.element.hidden = context.effects.length === 0;
|
||||||
|
if (options?.force) {
|
||||||
|
document.getElementById('ui-right-column-1')?.appendChild(this.element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,29 +10,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.effects.overlay = null;
|
this.effects.overlay = null;
|
||||||
|
|
||||||
// Categorize effects
|
// Categorize effects
|
||||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
const activeEffects = this.actor?.getActiveEffects() ?? [];
|
||||||
const activeEffects = (this.actor ? this.actor.effects.filter(x => !x.disabled) : []).reduce((acc, effect) => {
|
|
||||||
acc.push(effect);
|
|
||||||
|
|
||||||
const currentStatusActiveEffects = acc.filter(
|
|
||||||
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
|
|
||||||
);
|
|
||||||
for (var status of effect.statuses) {
|
|
||||||
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
|
|
||||||
const statusData = statusMap.get(status);
|
|
||||||
if (statusData) {
|
|
||||||
acc.push({
|
|
||||||
name: game.i18n.localize(statusData.name),
|
|
||||||
statuses: [status],
|
|
||||||
img: statusData.icon ?? statusData.img,
|
|
||||||
tint: effect.tint
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
||||||
|
|
||||||
// Draw effects
|
// Draw effects
|
||||||
|
|
@ -56,6 +34,69 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
||||||
this.renderFlags.set({ refreshEffects: true });
|
this.renderFlags.set({ refreshEffects: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the distance from this token to another token object.
|
||||||
|
* This value is corrected to handle alternate token sizes and other grid types
|
||||||
|
* according to the diagonal rules.
|
||||||
|
*/
|
||||||
|
distanceTo(target) {
|
||||||
|
if (!canvas.ready) return NaN;
|
||||||
|
if (this === target) return 0;
|
||||||
|
|
||||||
|
const originPoint = this.center;
|
||||||
|
const destinationPoint = target.center;
|
||||||
|
|
||||||
|
// Compute for gridless. This version returns circular edge to edge + grid distance,
|
||||||
|
// so that tokens that are touching return 5.
|
||||||
|
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
|
||||||
|
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
|
||||||
|
const originRadius = this.bounds.width * boundsCorrection / 2;
|
||||||
|
const targetRadius = target.bounds.width * boundsCorrection / 2;
|
||||||
|
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
|
||||||
|
return distance - originRadius - targetRadius + canvas.grid.distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute what the closest grid space of each token is, then compute that distance
|
||||||
|
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
|
||||||
|
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
|
||||||
|
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
|
||||||
|
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
|
||||||
|
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
|
||||||
|
});
|
||||||
|
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
|
||||||
|
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
|
||||||
|
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
|
||||||
|
});
|
||||||
|
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
|
||||||
|
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
|
||||||
|
const points = [
|
||||||
|
{ x: bounds.x, y: bounds.y },
|
||||||
|
{ x: bounds.x + bounds.width, y: bounds.y },
|
||||||
|
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
|
||||||
|
{ x: bounds.x, y: bounds.y + bounds.height }
|
||||||
|
];
|
||||||
|
const pairsToTest = [
|
||||||
|
[points[0], points[1]],
|
||||||
|
[points[1], points[2]],
|
||||||
|
[points[2], points[3]],
|
||||||
|
[points[3], points[0]]
|
||||||
|
];
|
||||||
|
for (const pair of pairsToTest) {
|
||||||
|
const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
|
||||||
|
if (result) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
|
||||||
|
isAdjacentWith(token) {
|
||||||
|
return this.distanceTo(token) <= (canvas.grid.distance * 1.5);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
_drawBar(number, bar, data) {
|
_drawBar(number, bar, data) {
|
||||||
const val = Number(data.value);
|
const val = Number(data.value);
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ export * as actionConfig from './actionConfig.mjs';
|
||||||
export * as actorConfig from './actorConfig.mjs';
|
export * as actorConfig from './actorConfig.mjs';
|
||||||
export * as domainConfig from './domainConfig.mjs';
|
export * as domainConfig from './domainConfig.mjs';
|
||||||
export * as effectConfig from './effectConfig.mjs';
|
export * as effectConfig from './effectConfig.mjs';
|
||||||
|
export * as encounterConfig from './encounterConfig.mjs';
|
||||||
export * as flagsConfig from './flagsConfig.mjs';
|
export * as flagsConfig from './flagsConfig.mjs';
|
||||||
export * as generalConfig from './generalConfig.mjs';
|
export * as generalConfig from './generalConfig.mjs';
|
||||||
|
export * as hooksConfig from './hooksConfig.mjs';
|
||||||
export * as itemConfig from './itemConfig.mjs';
|
export * as itemConfig from './itemConfig.mjs';
|
||||||
export * as settingsConfig from './settingsConfig.mjs';
|
export * as settingsConfig from './settingsConfig.mjs';
|
||||||
export * as systemConfig from './system.mjs';
|
export * as systemConfig from './system.mjs';
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ export const actionTypes = {
|
||||||
attack: {
|
attack: {
|
||||||
id: 'attack',
|
id: 'attack',
|
||||||
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
|
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
|
||||||
icon: 'fa-khanda',
|
icon: 'fa-hand-fist',
|
||||||
tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip'
|
tooltip: 'DAGGERHEART.ACTIONS.TYPES.attack.tooltip'
|
||||||
},
|
},
|
||||||
countdown: {
|
countdown: {
|
||||||
|
|
|
||||||
|
|
@ -108,52 +108,64 @@ export const adversaryTypes = {
|
||||||
bruiser: {
|
bruiser: {
|
||||||
id: 'bruiser',
|
id: 'bruiser',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description',
|
||||||
|
bpCost: 4
|
||||||
},
|
},
|
||||||
horde: {
|
horde: {
|
||||||
id: 'horde',
|
id: 'horde',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.horde.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.horde.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
leader: {
|
leader: {
|
||||||
id: 'leader',
|
id: 'leader',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.leader.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.leader.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.leader.description',
|
||||||
|
bpCost: 3,
|
||||||
|
bpDescription: 'DAGGERHEART.CONFIG.AdversaryType.leader.'
|
||||||
},
|
},
|
||||||
minion: {
|
minion: {
|
||||||
id: 'minion',
|
id: 'minion',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.minion.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.minion.description',
|
||||||
|
bpCost: 1,
|
||||||
|
partyAmountPerBP: true
|
||||||
},
|
},
|
||||||
ranged: {
|
ranged: {
|
||||||
id: 'ranged',
|
id: 'ranged',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
skulk: {
|
skulk: {
|
||||||
id: 'skulk',
|
id: 'skulk',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
social: {
|
social: {
|
||||||
id: 'social',
|
id: 'social',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.social.description',
|
||||||
|
bpCost: 1
|
||||||
},
|
},
|
||||||
solo: {
|
solo: {
|
||||||
id: 'solo',
|
id: 'solo',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.solo.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.solo.description',
|
||||||
|
bpCost: 5
|
||||||
},
|
},
|
||||||
standard: {
|
standard: {
|
||||||
id: 'standard',
|
id: 'standard',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.standard.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.standard.description',
|
||||||
|
bpCost: 2
|
||||||
},
|
},
|
||||||
support: {
|
support: {
|
||||||
id: 'support',
|
id: 'support',
|
||||||
label: 'DAGGERHEART.CONFIG.AdversaryType.support.label',
|
label: 'DAGGERHEART.CONFIG.AdversaryType.support.label',
|
||||||
description: 'DAGGERHEART.ACTORS.Adversary.support.description'
|
description: 'DAGGERHEART.ACTORS.Adversary.support.description',
|
||||||
|
bpCost: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -199,6 +211,44 @@ export const adversaryTraits = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const tokenSize = {
|
||||||
|
custom: {
|
||||||
|
id: 'custom',
|
||||||
|
value: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.custom'
|
||||||
|
},
|
||||||
|
tiny: {
|
||||||
|
id: 'tiny',
|
||||||
|
value: 1,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
id: 'small',
|
||||||
|
value: 2,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.small'
|
||||||
|
},
|
||||||
|
medium: {
|
||||||
|
id: 'medium',
|
||||||
|
value: 3,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
id: 'large',
|
||||||
|
value: 4,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.large'
|
||||||
|
},
|
||||||
|
huge: {
|
||||||
|
id: 'huge',
|
||||||
|
value: 5,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
|
||||||
|
},
|
||||||
|
gargantuan: {
|
||||||
|
id: 'gargantuan',
|
||||||
|
value: 6,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const levelChoices = {
|
export const levelChoices = {
|
||||||
attributes: {
|
attributes: {
|
||||||
name: 'attributes',
|
name: 'attributes',
|
||||||
|
|
|
||||||
146
module/config/encounterConfig.mjs
Normal file
146
module/config/encounterConfig.mjs
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
export const BaseBPPerEncounter = nrCharacters => 3 * nrCharacters + 2;
|
||||||
|
|
||||||
|
export const AdversaryBPPerEncounter = (adversaries, characters) => {
|
||||||
|
const adversaryTypes = CONFIG.DH.ACTOR.allAdversaryTypes();
|
||||||
|
return adversaries
|
||||||
|
.reduce((acc, adversary) => {
|
||||||
|
const existingEntry = acc.find(
|
||||||
|
x => x.adversary.name === adversary.name && x.adversary.type === adversary.type
|
||||||
|
);
|
||||||
|
if (existingEntry) {
|
||||||
|
existingEntry.nr += 1;
|
||||||
|
} else if (adversary.type) {
|
||||||
|
acc.push({ adversary, nr: 1 });
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.reduce((acc, entry) => {
|
||||||
|
const adversary = entry.adversary;
|
||||||
|
const type = adversaryTypes[adversary.type];
|
||||||
|
const bpCost = type.bpCost ?? 0;
|
||||||
|
if (type.partyAmountPerBP) {
|
||||||
|
acc += characters.length === 0 ? 0 : Math.ceil(entry.nr / characters.length);
|
||||||
|
} else {
|
||||||
|
acc += bpCost * entry.nr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adversaryTypeCostBrackets = {
|
||||||
|
1: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['minion'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.minion'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sort: 2,
|
||||||
|
types: ['social', 'support'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.support'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
2: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['horde', 'ranged', 'skulk', 'standard'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.standard'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
3: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['leader'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.leader'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
4: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['bruiser'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.bruiser'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
5: [
|
||||||
|
{
|
||||||
|
sort: 1,
|
||||||
|
types: ['solo'],
|
||||||
|
description: 'DAGGERHEART.CONFIG.AdversaryTypeCost.solo'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BPModifiers = {
|
||||||
|
[-2]: {
|
||||||
|
manySolos: {
|
||||||
|
sort: 1,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.manySolos.description',
|
||||||
|
automatic: true,
|
||||||
|
conditional: (_combat, adversaries) => {
|
||||||
|
return adversaries.filter(x => x.system.type === 'solo').length > 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
increaseDamage: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.description',
|
||||||
|
effectTargetTypes: ['adversary'],
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
name: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.name',
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.increaseDamage.effect.description',
|
||||||
|
img: 'icons/magic/control/buff-flight-wings-red.webp',
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: 'system.bonuses.damage.physical.dice',
|
||||||
|
mode: 2,
|
||||||
|
value: '1d4'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'system.bonuses.damage.magical.dice',
|
||||||
|
mode: 2,
|
||||||
|
value: '1d4'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[-1]: {
|
||||||
|
lessDifficult: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.lessDifficult.description'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1: {
|
||||||
|
lowerTier: {
|
||||||
|
sort: 1,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.lowerTier.description',
|
||||||
|
automatic: true,
|
||||||
|
conditional: (_combat, adversaries, characters) => {
|
||||||
|
const characterMaxTier = characters.reduce((maxTier, character) => {
|
||||||
|
return character.system.tier > maxTier ? character.system.tier : maxTier;
|
||||||
|
}, 1);
|
||||||
|
return adversaries.some(adversary => adversary.system.tier < characterMaxTier);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
noToughies: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.noToughies.description',
|
||||||
|
automatic: true,
|
||||||
|
conditional: (_combat, adversaries) => {
|
||||||
|
const toughyTypes = ['bruiser', 'horde', 'leader', 'solo'];
|
||||||
|
return (
|
||||||
|
adversaries.length > 0 &&
|
||||||
|
!adversaries.some(adversary => toughyTypes.includes(adversary.system.type))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
moreDangerous: {
|
||||||
|
sort: 2,
|
||||||
|
description: 'DAGGERHEART.CONFIG.BPModifiers.moreDangerous.description'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -26,3 +26,5 @@ export const userFlags = {
|
||||||
welcomeMessage: 'welcome-message',
|
welcomeMessage: 'welcome-message',
|
||||||
countdownMode: 'countdown-mode'
|
countdownMode: 'countdown-mode'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const combatToggle = 'combat-toggle-origin';
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -298,7 +298,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -341,7 +341,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
@ -407,7 +407,7 @@ export const defaultRestOptions = {
|
||||||
actionType: 'action',
|
actionType: 'action',
|
||||||
chatDisplay: false,
|
chatDisplay: false,
|
||||||
target: {
|
target: {
|
||||||
type: 'self'
|
type: 'friendly'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
parts: [
|
parts: [
|
||||||
|
|
|
||||||
5
module/config/hooksConfig.mjs
Normal file
5
module/config/hooksConfig.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const hooksConfig = {
|
||||||
|
effectDisplayToggle: 'DHEffectDisplayToggle'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hooksConfig;
|
||||||
|
|
@ -5,7 +5,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'damage',
|
type: 'damage',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
|
||||||
|
|
@ -174,7 +173,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
|
||||||
|
|
@ -188,7 +186,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
|
||||||
|
|
@ -231,7 +228,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
|
||||||
|
|
@ -269,7 +265,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
|
||||||
|
|
@ -306,7 +301,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'attack',
|
type: 'attack',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
|
||||||
|
|
@ -353,7 +347,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
|
||||||
|
|
@ -373,7 +366,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'attack',
|
type: 'attack',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
|
||||||
|
|
@ -401,7 +393,6 @@ export const armorFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
|
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
|
||||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
|
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
|
||||||
|
|
@ -444,7 +435,8 @@ export const armorFeatures = {
|
||||||
{
|
{
|
||||||
key: 'system.resistance.magical.reduction',
|
key: 'system.resistance.magical.reduction',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '@system.armorScore'
|
value: '@system.armorScore',
|
||||||
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -459,7 +451,16 @@ export const allArmorFeatures = () => {
|
||||||
...armorFeatures,
|
...armorFeatures,
|
||||||
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
||||||
const feature = homebrewFeatures[key];
|
const feature = homebrewFeatures[key];
|
||||||
acc[key] = { ...feature, label: feature.name };
|
const actions = feature.actions.map(action => ({
|
||||||
|
...action,
|
||||||
|
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
|
||||||
|
type: action.type
|
||||||
|
}));
|
||||||
|
const actionEffects = actions.flatMap(a => a.effects);
|
||||||
|
|
||||||
|
const effects = feature.effects.filter(effect => !actionEffects.some(x => x.id === effect.id));
|
||||||
|
|
||||||
|
acc[key] = { ...feature, label: feature.name, effects, actions };
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
};
|
};
|
||||||
|
|
@ -528,7 +529,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
|
||||||
|
|
@ -573,7 +573,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
|
||||||
|
|
@ -587,7 +586,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
|
||||||
|
|
@ -601,7 +599,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
|
||||||
|
|
@ -638,7 +635,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
|
||||||
|
|
@ -679,7 +675,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
|
||||||
|
|
@ -693,7 +688,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
|
||||||
|
|
@ -716,7 +710,8 @@ export const weaponFeatures = {
|
||||||
{
|
{
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '@system.armorScore'
|
value: '@system.armorScore',
|
||||||
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -730,7 +725,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'damage',
|
type: 'damage',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
|
||||||
|
|
@ -775,7 +769,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
|
||||||
|
|
@ -826,7 +819,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
|
||||||
|
|
@ -840,7 +832,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
|
||||||
|
|
@ -854,7 +845,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect', // Should prompt a dc 14 reaction save on adversaries
|
type: 'effect', // Should prompt a dc 14 reaction save on adversaries
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
|
||||||
|
|
@ -868,7 +858,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
|
||||||
|
|
@ -888,7 +877,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
||||||
|
|
@ -920,7 +908,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'healing',
|
type: 'healing',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
|
||||||
|
|
@ -968,7 +955,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
|
||||||
|
|
@ -982,7 +968,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
|
||||||
|
|
@ -996,7 +981,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
|
||||||
|
|
@ -1010,7 +994,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
|
||||||
|
|
@ -1024,7 +1007,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
|
||||||
|
|
@ -1038,7 +1020,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
|
||||||
|
|
@ -1052,7 +1033,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
|
||||||
|
|
@ -1090,7 +1070,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
|
||||||
|
|
@ -1136,7 +1115,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
|
||||||
|
|
@ -1150,7 +1128,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
|
||||||
|
|
@ -1187,7 +1164,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
|
||||||
|
|
@ -1231,7 +1207,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
|
||||||
|
|
@ -1269,7 +1244,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
|
||||||
|
|
@ -1283,7 +1257,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
|
||||||
|
|
@ -1297,7 +1270,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
|
||||||
|
|
@ -1311,7 +1283,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
|
||||||
|
|
@ -1355,7 +1326,8 @@ export const weaponFeatures = {
|
||||||
{
|
{
|
||||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '@system.traits.agility.value'
|
value: '@system.traits.agility.value',
|
||||||
|
priority: 21
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1367,7 +1339,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
|
||||||
|
|
@ -1381,7 +1352,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
|
||||||
|
|
@ -1401,7 +1371,6 @@ export const weaponFeatures = {
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
type: 'effect',
|
type: 'effect',
|
||||||
actionType: 'action',
|
|
||||||
chatDisplay: true,
|
chatDisplay: true,
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
|
||||||
|
|
@ -1414,11 +1383,21 @@ export const weaponFeatures = {
|
||||||
export const allWeaponFeatures = () => {
|
export const allWeaponFeatures = () => {
|
||||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||||
.weaponFeatures;
|
.weaponFeatures;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...weaponFeatures,
|
...weaponFeatures,
|
||||||
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
...Object.keys(homebrewFeatures).reduce((acc, key) => {
|
||||||
const feature = homebrewFeatures[key];
|
const feature = homebrewFeatures[key];
|
||||||
acc[key] = { ...feature, label: feature.name };
|
|
||||||
|
const actions = feature.actions.map(action => ({
|
||||||
|
...action,
|
||||||
|
effects: action.effects.map(effect => feature.effects.find(x => x.id === effect._id)),
|
||||||
|
type: action.type
|
||||||
|
}));
|
||||||
|
const actionEffects = actions.flatMap(a => a.effects);
|
||||||
|
const effects = feature.effects.filter(effect => !actionEffects.some(x => x.id === effect.id));
|
||||||
|
|
||||||
|
acc[key] = { ...feature, label: feature.name, effects, actions };
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
};
|
};
|
||||||
|
|
@ -1439,6 +1418,12 @@ export const orderedWeaponFeatures = () => {
|
||||||
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
|
return Object.values(all).sort((a, b) => game.i18n.localize(a.label).localeCompare(game.i18n.localize(b.label)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const featureForm = {
|
||||||
|
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
|
||||||
|
action: 'DAGGERHEART.CONFIG.FeatureForm.action',
|
||||||
|
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
|
||||||
|
};
|
||||||
|
|
||||||
export const featureTypes = {
|
export const featureTypes = {
|
||||||
ancestry: {
|
ancestry: {
|
||||||
id: 'ancestry',
|
id: 'ancestry',
|
||||||
|
|
@ -1496,21 +1481,6 @@ export const featureSubTypes = {
|
||||||
mastery: 'mastery'
|
mastery: 'mastery'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionTypes = {
|
|
||||||
passive: {
|
|
||||||
id: 'passive',
|
|
||||||
label: 'DAGGERHEART.CONFIG.ActionType.passive'
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
id: 'action',
|
|
||||||
label: 'DAGGERHEART.CONFIG.ActionType.action'
|
|
||||||
},
|
|
||||||
reaction: {
|
|
||||||
id: 'reaction',
|
|
||||||
label: 'DAGGERHEART.CONFIG.ActionType.reaction'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const itemResourceTypes = {
|
export const itemResourceTypes = {
|
||||||
simple: {
|
simple: {
|
||||||
id: 'simple',
|
id: 'simple',
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ export const gameSettings = {
|
||||||
LevelTiers: 'LevelTiers',
|
LevelTiers: 'LevelTiers',
|
||||||
Countdowns: 'Countdowns',
|
Countdowns: 'Countdowns',
|
||||||
LastMigrationVersion: 'LastMigrationVersion',
|
LastMigrationVersion: 'LastMigrationVersion',
|
||||||
TagTeamRoll: 'TagTeamRoll'
|
TagTeamRoll: 'TagTeamRoll',
|
||||||
|
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionAutomationChoices = {
|
export const actionAutomationChoices = {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
import * as GENERAL from './generalConfig.mjs';
|
import * as GENERAL from './generalConfig.mjs';
|
||||||
import * as DOMAIN from './domainConfig.mjs';
|
import * as DOMAIN from './domainConfig.mjs';
|
||||||
|
import * as ENCOUNTER from './encounterConfig.mjs';
|
||||||
import * as ACTOR from './actorConfig.mjs';
|
import * as ACTOR from './actorConfig.mjs';
|
||||||
import * as ITEM from './itemConfig.mjs';
|
import * as ITEM from './itemConfig.mjs';
|
||||||
import * as SETTINGS from './settingsConfig.mjs';
|
import * as SETTINGS from './settingsConfig.mjs';
|
||||||
import * as EFFECTS from './effectConfig.mjs';
|
import * as EFFECTS from './effectConfig.mjs';
|
||||||
import * as ACTIONS from './actionConfig.mjs';
|
import * as ACTIONS from './actionConfig.mjs';
|
||||||
import * as FLAGS from './flagsConfig.mjs';
|
import * as FLAGS from './flagsConfig.mjs';
|
||||||
import * as ITEMBROWSER from './itemBrowserConfig.mjs'
|
import HOOKS from './hooksConfig.mjs';
|
||||||
|
import * as ITEMBROWSER from './itemBrowserConfig.mjs';
|
||||||
|
|
||||||
export const SYSTEM_ID = 'daggerheart';
|
export const SYSTEM_ID = 'daggerheart';
|
||||||
|
|
||||||
export const SYSTEM = {
|
export const SYSTEM = {
|
||||||
id: SYSTEM_ID,
|
id: SYSTEM_ID,
|
||||||
|
ENCOUNTER,
|
||||||
GENERAL,
|
GENERAL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ACTOR,
|
ACTOR,
|
||||||
|
|
@ -20,5 +23,6 @@ export const SYSTEM = {
|
||||||
EFFECTS,
|
EFFECTS,
|
||||||
ACTIONS,
|
ACTIONS,
|
||||||
FLAGS,
|
FLAGS,
|
||||||
|
HOOKS,
|
||||||
ITEMBROWSER
|
ITEMBROWSER
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
initial: 'action',
|
initial: 'action',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true
|
required: true
|
||||||
})
|
}),
|
||||||
|
targetUuid: new fields.StringField({ initial: undefined })
|
||||||
};
|
};
|
||||||
|
|
||||||
this.extraSchemas.forEach(s => {
|
this.extraSchemas.forEach(s => {
|
||||||
|
|
@ -95,6 +96,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
prepareData() {
|
prepareData() {
|
||||||
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
||||||
this.img = this.img ?? this.parent?.parent?.img;
|
this.img = this.img ?? this.parent?.parent?.img;
|
||||||
|
|
||||||
|
/* Fallback to feature description */
|
||||||
|
this.description = this.description || this.parent?.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -159,12 +163,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
*/
|
*/
|
||||||
getRollData(data = {}) {
|
getRollData(data = {}) {
|
||||||
if (!this.actor) return null;
|
const actorData = this.actor ? this.actor.getRollData(false) : {};
|
||||||
const actorData = this.actor.getRollData(false);
|
|
||||||
|
|
||||||
// Add Roll results to RollDatas
|
|
||||||
actorData.result = data.roll?.total ?? 1;
|
actorData.result = data.roll?.total ?? 1;
|
||||||
|
|
||||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||||
: 1;
|
: 1;
|
||||||
|
|
@ -193,8 +194,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
async use(event) {
|
async use(event) {
|
||||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
if (this.chatDisplay) await this.toChat();
|
|
||||||
|
|
||||||
let config = this.prepareConfig(event);
|
let config = this.prepareConfig(event);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
|
|
||||||
|
|
@ -208,9 +207,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
|
|
||||||
// Execute the Action Worflow in order based of schema fields
|
// Execute the Action Worflow in order based of schema fields
|
||||||
await this.executeWorkflow(config);
|
await this.executeWorkflow(config);
|
||||||
|
await config.resourceUpdates.updateResources();
|
||||||
|
|
||||||
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) await this.toChat();
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,8 +241,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
isDirect: !!this.damage?.direct,
|
isDirect: !!this.damage?.direct,
|
||||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll
|
evaluate: this.hasRoll,
|
||||||
|
resourceUpdates: new ResourceUpdateMap(this.actor),
|
||||||
|
targetUuid: this.targetUuid
|
||||||
};
|
};
|
||||||
|
|
||||||
DHBaseAction.applyKeybindings(config);
|
DHBaseAction.applyKeybindings(config);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
@ -322,11 +327,46 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
* @returns {string[]} An array of localized tag strings.
|
* @returns {string[]} An array of localized tag strings.
|
||||||
*/
|
*/
|
||||||
_getTags() {
|
_getTags() {
|
||||||
const tags = [
|
const tags = [game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`)];
|
||||||
game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`),
|
|
||||||
game.i18n.localize(`DAGGERHEART.CONFIG.ActionType.${this.actionType}`)
|
|
||||||
];
|
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ResourceUpdateMap extends Map {
|
||||||
|
#actor;
|
||||||
|
|
||||||
|
constructor(actor) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.#actor = actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
addResources(resources) {
|
||||||
|
for (const resource of resources) {
|
||||||
|
if (!resource.key) continue;
|
||||||
|
|
||||||
|
const existing = this.get(resource.key);
|
||||||
|
if (existing) {
|
||||||
|
this.set(resource.key, {
|
||||||
|
...existing,
|
||||||
|
value: existing.value + (resource.value ?? 0),
|
||||||
|
total: existing.total + (resource.total ?? 0)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.set(resource.key, resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#getResources() {
|
||||||
|
return Array.from(this.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateResources() {
|
||||||
|
if (this.#actor) {
|
||||||
|
const target = this.#actor.system.partner ?? this.#actor;
|
||||||
|
await target.modifyResource(this.#getResources());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,17 @@ import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHDamageAction extends DHBaseAction {
|
export default class DHDamageAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
|
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a display ready damage formula string
|
||||||
|
* @returns Formula string
|
||||||
|
*/
|
||||||
|
getDamageFormula() {
|
||||||
|
const strings = [];
|
||||||
|
for (const { value } of this.damage.parts) {
|
||||||
|
strings.push(Roll.replaceFormulaData(value.getFormula(), this.actor?.getRollData() ?? {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.join(' + ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,17 @@
|
||||||
|
/** -- Changes Type Priorities --
|
||||||
|
* - Base Number -
|
||||||
|
* Custom: 0
|
||||||
|
* Multiply: 10
|
||||||
|
* Add: 20
|
||||||
|
* Downgrade: 30
|
||||||
|
* Upgrade: 40
|
||||||
|
* Override: 50
|
||||||
|
*
|
||||||
|
* - Changes Value Priorities -
|
||||||
|
* Standard: +0
|
||||||
|
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
|
||||||
|
*/
|
||||||
|
|
||||||
export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
export default class BaseEffect extends foundry.abstract.TypeDataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
base64: false
|
base64: false
|
||||||
}),
|
}),
|
||||||
tokenSize: new fields.SchemaField({
|
tokenSize: new fields.SchemaField({
|
||||||
height: new fields.NumberField({ integer: true, nullable: true }),
|
height: new fields.NumberField({ integer: false, nullable: true }),
|
||||||
width: new fields.NumberField({ integer: true, nullable: true })
|
width: new fields.NumberField({ integer: false, nullable: true })
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
advantageOn: new fields.ArrayField(new fields.StringField()),
|
||||||
|
|
@ -29,6 +29,14 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
static migrateData(source) {
|
||||||
|
if (!source.characterTokenData.tokenSize.height) source.characterTokenData.tokenSize.height = 1;
|
||||||
|
if (!source.characterTokenData.tokenSize.width) source.characterTokenData.tokenSize.width = 1;
|
||||||
|
|
||||||
|
return super.migrateData(source);
|
||||||
|
}
|
||||||
|
|
||||||
async _onCreate(_data, _options, userId) {
|
async _onCreate(_data, _options, userId) {
|
||||||
if (userId !== game.user.id) return;
|
if (userId !== game.user.id) return;
|
||||||
|
|
||||||
|
|
@ -57,20 +65,38 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateToken = token => ({
|
const updateToken = token => {
|
||||||
...baseUpdate,
|
let x = null,
|
||||||
'texture': {
|
y = null;
|
||||||
enabled: this.characterTokenData.usesDynamicToken,
|
if (token.object?.scene?.grid) {
|
||||||
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg
|
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
|
||||||
},
|
token.object.scene.grid,
|
||||||
'ring': {
|
{ x: token.x, y: token.y, elevation: token.elevation },
|
||||||
subject: {
|
baseUpdate.width,
|
||||||
texture:
|
baseUpdate.height
|
||||||
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg
|
);
|
||||||
}
|
|
||||||
},
|
x = positionData.x;
|
||||||
'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
|
y = positionData.y;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...baseUpdate,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
'texture': {
|
||||||
|
enabled: this.characterTokenData.usesDynamicToken,
|
||||||
|
src: token.flags.daggerheart?.beastformTokenImg ?? this.characterTokenData.tokenImg
|
||||||
|
},
|
||||||
|
'ring': {
|
||||||
|
subject: {
|
||||||
|
texture:
|
||||||
|
token.flags.daggerheart?.beastformSubjectTexture ?? this.characterTokenData.tokenRingImg
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
await updateActorTokens(this.parent.parent, update, updateToken);
|
await updateActorTokens(this.parent.parent, update, updateToken);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
|
|
||||||
export default class DhpAdversary extends BaseDataActor {
|
export default class DhpAdversary extends BaseDataActor {
|
||||||
|
|
@ -11,7 +11,8 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
label: 'TYPES.Actor.adversary',
|
label: 'TYPES.Actor.adversary',
|
||||||
type: 'adversary',
|
type: 'adversary',
|
||||||
settingSheet: DHAdversarySettings,
|
settingSheet: DHAdversarySettings,
|
||||||
hasAttribution: true
|
hasAttribution: true,
|
||||||
|
usesSize: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,27 +56,11 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
resources: new fields.SchemaField({
|
resources: new fields.SchemaField({
|
||||||
hitPoints: resourceField(
|
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||||
0,
|
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
|
||||||
0,
|
|
||||||
'DAGGERHEART.GENERAL.HitPoints.plural',
|
|
||||||
true,
|
|
||||||
game.i18n.localize('DAGGERHEART.GENERAL.max')
|
|
||||||
),
|
|
||||||
stress: resourceField(
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
'DAGGERHEART.GENERAL.stress',
|
|
||||||
true,
|
|
||||||
game.i18n.localize('DAGGERHEART.GENERAL.max')
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
conditionImmunities: new fields.SchemaField({
|
...commonActorRules()
|
||||||
hidden: new fields.BooleanField({ initial: false }),
|
|
||||||
restrained: new fields.BooleanField({ initial: false }),
|
|
||||||
vulnerable: new fields.BooleanField({ initial: false })
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
attack: new ActionField({
|
attack: new ActionField({
|
||||||
initial: {
|
initial: {
|
||||||
|
|
@ -141,6 +126,10 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
return this.parent.items.filter(x => x.type === 'feature');
|
return this.parent.items.filter(x => x.type === 'feature');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isItemValid(source) {
|
||||||
|
return source.type === 'feature';
|
||||||
|
}
|
||||||
|
|
||||||
async _preUpdate(changes, options, user) {
|
async _preUpdate(changes, options, user) {
|
||||||
const allowed = await super._preUpdate(changes, options, user);
|
const allowed = await super._preUpdate(changes, options, user);
|
||||||
if (allowed === false) return false;
|
if (allowed === false) return false;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,24 @@
|
||||||
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
||||||
|
import DHItem from '../../documents/item.mjs';
|
||||||
import { getScrollTextData } from '../../helpers/utils.mjs';
|
import { getScrollTextData } from '../../helpers/utils.mjs';
|
||||||
|
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
||||||
new foundry.data.fields.SchemaField({
|
new fields.SchemaField({
|
||||||
resistance: new foundry.data.fields.BooleanField({
|
resistance: new fields.BooleanField({
|
||||||
initial: false,
|
initial: false,
|
||||||
label: `${resistanceLabel}.label`,
|
label: `${resistanceLabel}.label`,
|
||||||
hint: `${resistanceLabel}.hint`,
|
hint: `${resistanceLabel}.hint`,
|
||||||
isAttributeChoice: true
|
isAttributeChoice: true
|
||||||
}),
|
}),
|
||||||
immunity: new foundry.data.fields.BooleanField({
|
immunity: new fields.BooleanField({
|
||||||
initial: false,
|
initial: false,
|
||||||
label: `${immunityLabel}.label`,
|
label: `${immunityLabel}.label`,
|
||||||
hint: `${immunityLabel}.hint`,
|
hint: `${immunityLabel}.hint`,
|
||||||
isAttributeChoice: true
|
isAttributeChoice: true
|
||||||
}),
|
}),
|
||||||
reduction: new foundry.data.fields.NumberField({
|
reduction: new fields.NumberField({
|
||||||
integer: true,
|
integer: true,
|
||||||
initial: 0,
|
initial: 0,
|
||||||
label: `${reductionLabel}.label`,
|
label: `${reductionLabel}.label`,
|
||||||
|
|
@ -23,6 +26,25 @@ const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Common rules applying to Characters and Adversaries */
|
||||||
|
export const commonActorRules = (extendedData = { damageReduction: {} }) => ({
|
||||||
|
conditionImmunities: new fields.SchemaField({
|
||||||
|
hidden: new fields.BooleanField({ initial: false }),
|
||||||
|
restrained: new fields.BooleanField({ initial: false }),
|
||||||
|
vulnerable: new fields.BooleanField({ initial: false })
|
||||||
|
}),
|
||||||
|
damageReduction: new fields.SchemaField({
|
||||||
|
thresholdImmunities: new fields.SchemaField({
|
||||||
|
minor: new fields.BooleanField({ initial: false })
|
||||||
|
}),
|
||||||
|
reduceSeverity: new fields.SchemaField({
|
||||||
|
magical: new fields.NumberField({ initial: 0, min: 0 }),
|
||||||
|
physical: new fields.NumberField({ initial: 0, min: 0 })
|
||||||
|
}),
|
||||||
|
...extendedData.damageReduction
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes metadata about the actor data model type
|
* Describes metadata about the actor data model type
|
||||||
* @typedef {Object} ActorDataModelMetadata
|
* @typedef {Object} ActorDataModelMetadata
|
||||||
|
|
@ -41,7 +63,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
settingSheet: null,
|
settingSheet: null,
|
||||||
hasResistances: true,
|
hasResistances: true,
|
||||||
hasAttribution: false,
|
hasAttribution: false,
|
||||||
hasLimitedView: true
|
hasLimitedView: true,
|
||||||
|
usesSize: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +75,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
|
||||||
const schema = {};
|
const schema = {};
|
||||||
|
|
||||||
if (this.metadata.hasAttribution) {
|
if (this.metadata.hasAttribution) {
|
||||||
|
|
@ -76,6 +98,13 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
'DAGGERHEART.GENERAL.DamageResistance.magicalReduction'
|
'DAGGERHEART.GENERAL.DamageResistance.magicalReduction'
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
if (this.metadata.usesSize)
|
||||||
|
schema.size = new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.ACTOR.tokenSize,
|
||||||
|
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
|
||||||
|
});
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +135,17 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an item is available for use, such as multiclass features being disabled
|
||||||
|
* on a character.
|
||||||
|
*
|
||||||
|
* @param {DHItem} item The item being checked for availability
|
||||||
|
* @return {boolean} whether the item is available
|
||||||
|
*/
|
||||||
|
isItemAvailable(item) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
/* Clear all partyMembers from tagTeam setting.*/
|
/* Clear all partyMembers from tagTeam setting.*/
|
||||||
/* Revisit this when tagTeam is improved for many parties */
|
/* Revisit this when tagTeam is improved for many parties */
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { burden } from '../../config/generalConfig.mjs';
|
import { burden } from '../../config/generalConfig.mjs';
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor, { commonActorRules } from './base.mjs';
|
||||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||||
import { ActionField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||||
|
|
@ -217,44 +217,41 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}),
|
}),
|
||||||
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
||||||
rules: new fields.SchemaField({
|
rules: new fields.SchemaField({
|
||||||
damageReduction: new fields.SchemaField({
|
...commonActorRules({
|
||||||
maxArmorMarked: new fields.SchemaField({
|
damageReduction: {
|
||||||
value: new fields.NumberField({
|
magical: new fields.BooleanField({ initial: false }),
|
||||||
required: true,
|
physical: new fields.BooleanField({ initial: false }),
|
||||||
|
maxArmorMarked: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
initial: 1,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
||||||
|
}),
|
||||||
|
stressExtra: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
integer: true,
|
||||||
|
initial: 0,
|
||||||
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
stressDamageReduction: new fields.SchemaField({
|
||||||
|
severe: stressDamageReductionRule(
|
||||||
|
'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'
|
||||||
|
),
|
||||||
|
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
||||||
|
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
||||||
|
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
||||||
|
}),
|
||||||
|
increasePerArmorMark: new fields.NumberField({
|
||||||
integer: true,
|
integer: true,
|
||||||
initial: 1,
|
initial: 1,
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||||
|
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||||
}),
|
}),
|
||||||
stressExtra: new fields.NumberField({
|
disabledArmor: new fields.BooleanField({ intial: false })
|
||||||
required: true,
|
}
|
||||||
integer: true,
|
|
||||||
initial: 0,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
stressDamageReduction: new fields.SchemaField({
|
|
||||||
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
|
|
||||||
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
|
||||||
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
|
||||||
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
|
||||||
}),
|
|
||||||
increasePerArmorMark: new fields.NumberField({
|
|
||||||
integer: true,
|
|
||||||
initial: 1,
|
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
|
||||||
}),
|
|
||||||
magical: new fields.BooleanField({ initial: false }),
|
|
||||||
physical: new fields.BooleanField({ initial: false }),
|
|
||||||
thresholdImmunities: new fields.SchemaField({
|
|
||||||
minor: new fields.BooleanField({ initial: false })
|
|
||||||
}),
|
|
||||||
reduceSeverity: new fields.SchemaField({
|
|
||||||
magical: new fields.NumberField({ initial: 0, min: 0 }),
|
|
||||||
physical: new fields.NumberField({ initial: 0, min: 0 })
|
|
||||||
}),
|
|
||||||
disabledArmor: new fields.BooleanField({ intial: false })
|
|
||||||
}),
|
}),
|
||||||
attack: new fields.SchemaField({
|
attack: new fields.SchemaField({
|
||||||
damage: new fields.SchemaField({
|
damage: new fields.SchemaField({
|
||||||
|
|
@ -283,11 +280,6 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
conditionImmunities: new fields.SchemaField({
|
|
||||||
hidden: new fields.BooleanField({ initial: false }),
|
|
||||||
restrained: new fields.BooleanField({ initial: false }),
|
|
||||||
vulnerable: new fields.BooleanField({ initial: false })
|
|
||||||
}),
|
|
||||||
runeWard: new fields.BooleanField({ initial: false }),
|
runeWard: new fields.BooleanField({ initial: false }),
|
||||||
burden: new fields.SchemaField({
|
burden: new fields.SchemaField({
|
||||||
ignore: new fields.BooleanField()
|
ignore: new fields.BooleanField()
|
||||||
|
|
@ -435,6 +427,33 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
return attack;
|
return attack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
isItemAvailable(item) {
|
||||||
|
if (!super.isItemAvailable(this)) return false;
|
||||||
|
/**
|
||||||
|
* Preventing subclass features from being available if the chacaracter does not
|
||||||
|
* have the right subclass advancement
|
||||||
|
*/
|
||||||
|
if (item.system.originItemType !== CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!this.class.subclass) return false;
|
||||||
|
|
||||||
|
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
|
||||||
|
const subclassState = this[prop].subclass?.system?.featureState;
|
||||||
|
if (!subclassState) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
||||||
|
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
|
||||||
|
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get sheetLists() {
|
get sheetLists() {
|
||||||
const ancestryFeatures = [],
|
const ancestryFeatures = [],
|
||||||
communityFeatures = [],
|
communityFeatures = [],
|
||||||
|
|
@ -443,7 +462,7 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
companionFeatures = [],
|
companionFeatures = [],
|
||||||
features = [];
|
features = [];
|
||||||
|
|
||||||
for (let item of this.parent.items) {
|
for (let item of this.parent.items.filter(x => this.isItemAvailable(x))) {
|
||||||
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
|
||||||
ancestryFeatures.push(item);
|
ancestryFeatures.push(item);
|
||||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
|
||||||
|
|
@ -451,20 +470,7 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
|
||||||
classFeatures.push(item);
|
classFeatures.push(item);
|
||||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
|
||||||
if (this.class.subclass) {
|
subclassFeatures.push(item);
|
||||||
const prop = item.system.multiclassOrigin ? 'multiclass' : 'class';
|
|
||||||
const subclassState = this[prop].subclass?.system?.featureState;
|
|
||||||
if (!subclassState) continue;
|
|
||||||
|
|
||||||
if (
|
|
||||||
item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.foundation ||
|
|
||||||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
|
|
||||||
subclassState >= 2) ||
|
|
||||||
(item.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
|
|
||||||
) {
|
|
||||||
subclassFeatures.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
|
||||||
companionFeatures.push(item);
|
companionFeatures.push(item);
|
||||||
} else if (item.type === 'feature' && !item.system.type) {
|
} else if (item.type === 'feature' && !item.system.type) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import { ActionField, ActionsField } from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
|
|
@ -108,6 +108,10 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
get proficiency() {
|
get proficiency() {
|
||||||
return this.partner?.system?.proficiency ?? 1;
|
return this.partner?.system?.proficiency ?? 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isItemValid() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||||
|
|
|
||||||
|
|
@ -51,4 +51,8 @@ export default class DhEnvironment extends BaseDataActor {
|
||||||
get features() {
|
get features() {
|
||||||
return this.parent.items.filter(x => x.type === 'feature');
|
return this.parent.items.filter(x => x.type === 'feature');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isItemValid(source) {
|
||||||
|
return source.type === "feature";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ export default class DhParty extends BaseDataActor {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
isItemValid(source) {
|
||||||
|
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type);
|
||||||
|
}
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
super.prepareBaseData();
|
super.prepareBaseData();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,40 @@
|
||||||
export default class DhCombat extends foundry.abstract.TypeDataModel {
|
export default class DhCombat extends foundry.abstract.TypeDataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {};
|
return {
|
||||||
|
battleToggles: new fields.ArrayField(
|
||||||
|
new fields.SchemaField({
|
||||||
|
category: new fields.NumberField({ required: true, integer: true }),
|
||||||
|
grouping: new fields.StringField({ required: true })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Includes automatic BPModifiers */
|
||||||
|
get extendedBattleToggles() {
|
||||||
|
const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers;
|
||||||
|
const adversaries =
|
||||||
|
this.parent.turns?.filter(x => x.actor && x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ??
|
||||||
|
[];
|
||||||
|
const characters = this.parent.turns?.filter(x => x.actor && !x.isNPC) ?? [];
|
||||||
|
|
||||||
|
const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => {
|
||||||
|
const category = modifiers[categoryKey];
|
||||||
|
acc.push(
|
||||||
|
...Object.keys(category).reduce((acc, groupingKey) => {
|
||||||
|
const grouping = category[groupingKey];
|
||||||
|
if (grouping.automatic && grouping.conditional?.(this.parent, adversaries, characters)) {
|
||||||
|
acc.push({ category: Number(categoryKey), grouping: groupingKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [...this.battleToggles, ...activeAutomatic];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export default class BeastformField extends fields.SchemaField {
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
static async transform(selectedForm, evolvedData, hybridData) {
|
static async transform(selectedForm, evolvedData, hybridData) {
|
||||||
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm;
|
const formData = evolvedData?.form ?? selectedForm;
|
||||||
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
||||||
if (!beastformEffect) {
|
if (!beastformEffect) {
|
||||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||||
|
|
@ -92,6 +92,18 @@ export default class BeastformField extends fields.SchemaField {
|
||||||
|
|
||||||
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
||||||
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
|
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
|
||||||
|
|
||||||
|
const baseSize = evolvedData.form.system.tokenSize.size;
|
||||||
|
const evolvedSize =
|
||||||
|
baseSize === 'custom'
|
||||||
|
? 'custom'
|
||||||
|
: (Object.keys(CONFIG.DH.ACTOR.tokenSize).find(
|
||||||
|
x => CONFIG.DH.ACTOR.tokenSize[x].value === CONFIG.DH.ACTOR.tokenSize[baseSize].value + 1
|
||||||
|
) ?? baseSize);
|
||||||
|
formData.system.tokenSize = {
|
||||||
|
...evolvedData.form.system.tokenSize,
|
||||||
|
size: evolvedSize
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
|
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export default class CostField extends fields.ArrayField {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
await actor.modifyResource(resources);
|
config.resourceUpdates.addResources(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export default class SaveField extends fields.SchemaField {
|
||||||
if (SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) {
|
if (SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) {
|
||||||
targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
|
targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
|
||||||
await SaveField.rollAllSave.call(this, targets, config.event, message);
|
await SaveField.rollAllSave.call(this, targets, config.event, message);
|
||||||
} else return false;
|
} else return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -124,29 +124,21 @@ export default class SaveField extends fields.SchemaField {
|
||||||
*/
|
*/
|
||||||
static async updateSaveMessage(result, message, targetId) {
|
static async updateSaveMessage(result, message, targetId) {
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
const updateMsg = async function (message, targetId, result) {
|
|
||||||
// setTimeout(async () => {
|
const chatMessage = ui.chat.collection.get(message._id),
|
||||||
const chatMessage = ui.chat.collection.get(message._id),
|
changes = {
|
||||||
changes = {
|
flags: {
|
||||||
flags: {
|
[game.system.id]: {
|
||||||
[game.system.id]: {
|
reactionRolls: {
|
||||||
reactionRolls: {
|
[targetId]: {
|
||||||
[targetId]: {
|
result: result.roll.total,
|
||||||
result: result.roll.total,
|
success: result.roll.success
|
||||||
success: result.roll.success
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
await chatMessage.update(changes);
|
};
|
||||||
// }, 100);
|
await chatMessage.update(changes);
|
||||||
};
|
|
||||||
if (game.modules.get('dice-so-nice')?.active)
|
|
||||||
game.dice3d
|
|
||||||
.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id)
|
|
||||||
.then(async () => await updateMsg(message, targetId, result));
|
|
||||||
else await updateMsg(message, targetId, result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,12 @@ export default class TargetField extends fields.SchemaField {
|
||||||
config.hasTarget = true;
|
config.hasTarget = true;
|
||||||
let targets;
|
let targets;
|
||||||
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens
|
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens
|
||||||
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
|
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) {
|
||||||
targets = [this.actor.token ?? this.actor.prototypeToken];
|
targets = [this.actor.token ?? this.actor.prototypeToken];
|
||||||
else {
|
} else if (config.targetUuid) {
|
||||||
|
const actor = fromUuidSync(config.targetUuid);
|
||||||
|
targets = [actor.token ?? actor.prototypeToken];
|
||||||
|
} else {
|
||||||
targets = Array.from(game.user.targets);
|
targets = Array.from(game.user.targets);
|
||||||
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
|
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
|
||||||
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
|
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
|
||||||
|
|
@ -79,7 +82,7 @@ export default class TargetField extends fields.SchemaField {
|
||||||
return {
|
return {
|
||||||
id: token.id,
|
id: token.id,
|
||||||
actorId: token.actor.uuid,
|
actorId: token.actor.uuid,
|
||||||
name: token.actor.name,
|
name: token.name,
|
||||||
img: token.actor.img,
|
img: token.actor.img,
|
||||||
difficulty: token.actor.system.difficulty,
|
difficulty: token.actor.system.difficulty,
|
||||||
evasion: token.actor.system.evasion,
|
evasion: token.actor.system.evasion,
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,12 @@ export function ActionMixin(Base) {
|
||||||
return this.documentName;
|
return this.documentName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Getter for icons
|
||||||
|
get typeIcon() {
|
||||||
|
const config = CONFIG.DH.ACTIONS.actionTypes[this.type];
|
||||||
|
return config?.icon || 'fa-question'; // Fallback icon just in case
|
||||||
|
}
|
||||||
|
|
||||||
get relativeUUID() {
|
get relativeUUID() {
|
||||||
return `.Item.${this.item.id}.Action.${this.id}`;
|
return `.Item.${this.item.id}.Action.${this.id}`;
|
||||||
}
|
}
|
||||||
|
|
@ -256,7 +262,7 @@ export function ActionMixin(Base) {
|
||||||
async toChat(origin) {
|
async toChat(origin) {
|
||||||
const cls = getDocumentClass('ChatMessage');
|
const cls = getDocumentClass('ChatMessage');
|
||||||
const systemData = {
|
const systemData = {
|
||||||
title: game.i18n.localize('DAGGERHEART.CONFIG.ActionType.action'),
|
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||||
origin: origin,
|
origin: origin,
|
||||||
action: {
|
action: {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
|
@ -266,12 +272,17 @@ export function ActionMixin(Base) {
|
||||||
itemOrigin: this.item,
|
itemOrigin: this.item,
|
||||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const speaker = cls.getSpeaker();
|
||||||
const msg = {
|
const msg = {
|
||||||
type: 'abilityUse',
|
type: 'abilityUse',
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
actor: { name: this.actor.name, img: this.actor.img },
|
actor: { name: this.actor.name, img: this.actor.img },
|
||||||
author: this.author,
|
author: this.author,
|
||||||
speaker: cls.getSpeaker(),
|
speaker: {
|
||||||
|
speaker,
|
||||||
|
actor: speaker.actor ?? this.actor
|
||||||
|
},
|
||||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
|
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
|
||||||
system: systemData,
|
system: systemData,
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
content: await foundry.applications.handlebars.renderTemplate(
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,12 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
base64: false
|
base64: false
|
||||||
}),
|
}),
|
||||||
tokenSize: new fields.SchemaField({
|
tokenSize: new fields.SchemaField({
|
||||||
|
size: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
nullable: false,
|
||||||
|
choices: CONFIG.DH.ACTOR.tokenSize,
|
||||||
|
initial: CONFIG.DH.ACTOR.tokenSize.custom.id
|
||||||
|
}),
|
||||||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||||
}),
|
}),
|
||||||
|
|
@ -190,9 +196,18 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
|
|
||||||
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
|
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
|
||||||
|
|
||||||
|
const autoTokenSize =
|
||||||
|
this.tokenSize.size !== 'custom'
|
||||||
|
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[
|
||||||
|
this.tokenSize.size
|
||||||
|
]
|
||||||
|
: null;
|
||||||
|
const width = autoTokenSize ?? this.tokenSize.width;
|
||||||
|
const height = autoTokenSize ?? this.tokenSize.height;
|
||||||
|
|
||||||
const prototypeTokenUpdate = {
|
const prototypeTokenUpdate = {
|
||||||
height: this.tokenSize.height,
|
height,
|
||||||
width: this.tokenSize.width,
|
width,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.tokenImg
|
src: this.tokenImg
|
||||||
},
|
},
|
||||||
|
|
@ -202,16 +217,33 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const tokenUpdate = token => {
|
||||||
|
let x = null,
|
||||||
|
y = null;
|
||||||
|
if (token.object?.scene?.grid) {
|
||||||
|
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
|
||||||
|
token.object.scene.grid,
|
||||||
|
{ x: token.x, y: token.y, elevation: token.elevation },
|
||||||
|
width ?? token.width,
|
||||||
|
height ?? token.height
|
||||||
|
);
|
||||||
|
|
||||||
const tokenUpdate = token => ({
|
x = positionData.x;
|
||||||
...prototypeTokenUpdate,
|
y = positionData.y;
|
||||||
flags: {
|
|
||||||
daggerheart: {
|
|
||||||
beastformTokenImg: token.texture.src,
|
|
||||||
beastformSubjectTexture: token.ring.subject.texture
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
return {
|
||||||
|
...prototypeTokenUpdate,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
flags: {
|
||||||
|
daggerheart: {
|
||||||
|
beastformTokenImg: token.texture.src,
|
||||||
|
beastformSubjectTexture: token.ring.subject.texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate);
|
await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,10 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateDomainCard'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateDomainCard'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.actor.system.loadoutSlot.available) {
|
||||||
|
data.system.inVault = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,13 @@ export default class DHFeature extends BaseDataItem {
|
||||||
initial: null
|
initial: null
|
||||||
}),
|
}),
|
||||||
multiclassOrigin: new fields.BooleanField({ initial: false }),
|
multiclassOrigin: new fields.BooleanField({ initial: false }),
|
||||||
identifier: new fields.StringField()
|
identifier: new fields.StringField(),
|
||||||
|
featureForm: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
initial: 'passive',
|
||||||
|
choices: CONFIG.DH.ITEM.featureForm,
|
||||||
|
label: 'DAGGERHEART.CONFIG.FeatureForm.label'
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { defaultRestOptions } from '../../config/generalConfig.mjs';
|
import { defaultRestOptions } from '../../config/generalConfig.mjs';
|
||||||
import { ActionsField } from '../fields/actionField.mjs';
|
import { ActionsField } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
const currencyField = (initial, label) =>
|
const currencyField = (initial, label, icon) =>
|
||||||
new foundry.data.fields.SchemaField({
|
new foundry.data.fields.SchemaField({
|
||||||
enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }),
|
enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }),
|
||||||
label: new foundry.data.fields.StringField({
|
label: new foundry.data.fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
initial,
|
initial,
|
||||||
label
|
label
|
||||||
})
|
}),
|
||||||
|
icon: new foundry.data.fields.StringField({ required: true, nullable: false, blank: true, initial: icon })
|
||||||
});
|
});
|
||||||
|
|
||||||
export default class DhHomebrew extends foundry.abstract.DataModel {
|
export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||||
|
|
@ -39,16 +40,60 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||||
traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), {
|
traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), {
|
||||||
initial: () => [2, 1, 1, 0, 0, -1]
|
initial: () => [2, 1, 1, 0, 0, -1]
|
||||||
}),
|
}),
|
||||||
|
tokenSizes: new fields.SchemaField({
|
||||||
|
tiny: new fields.NumberField({
|
||||||
|
integer: false,
|
||||||
|
initial: 0.5,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
|
||||||
|
}),
|
||||||
|
small: new fields.NumberField({
|
||||||
|
integer: false,
|
||||||
|
initial: 0.8,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.small'
|
||||||
|
}),
|
||||||
|
medium: new fields.NumberField({
|
||||||
|
integer: false,
|
||||||
|
initial: 1,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
|
||||||
|
}),
|
||||||
|
large: new fields.NumberField({
|
||||||
|
integer: false,
|
||||||
|
initial: 2,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.large'
|
||||||
|
}),
|
||||||
|
huge: new fields.NumberField({
|
||||||
|
integer: false,
|
||||||
|
initial: 3,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
|
||||||
|
}),
|
||||||
|
gargantuan: new fields.NumberField({
|
||||||
|
integer: false,
|
||||||
|
initial: 4,
|
||||||
|
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
|
||||||
|
})
|
||||||
|
}),
|
||||||
currency: new fields.SchemaField({
|
currency: new fields.SchemaField({
|
||||||
title: new fields.StringField({
|
title: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
initial: 'Gold',
|
initial: 'Gold',
|
||||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName'
|
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName'
|
||||||
}),
|
}),
|
||||||
coins: currencyField('Coins', 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'),
|
coins: currencyField(
|
||||||
handfuls: currencyField('Handfuls', 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'),
|
'Coins',
|
||||||
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'),
|
'DAGGERHEART.SETTINGS.Homebrew.currency.coinName',
|
||||||
chests: currencyField('Chests', 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName')
|
'fa-solid fa-coin-front'
|
||||||
|
),
|
||||||
|
handfuls: currencyField(
|
||||||
|
'Handfuls',
|
||||||
|
'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName',
|
||||||
|
'fa-solid fa-coins'
|
||||||
|
),
|
||||||
|
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName', 'fa-solid fa-sack'),
|
||||||
|
chests: currencyField(
|
||||||
|
'Chests',
|
||||||
|
'DAGGERHEART.SETTINGS.Homebrew.currency.chestName',
|
||||||
|
'fa-solid fa-treasure-chest'
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
restMoves: new fields.SchemaField({
|
restMoves: new fields.SchemaField({
|
||||||
longRest: new fields.SchemaField({
|
longRest: new fields.SchemaField({
|
||||||
|
|
@ -139,22 +184,10 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
_initializeSource(source, options = {}) {
|
_initializeSource(source, options = {}) {
|
||||||
source = super._initializeSource(source, options);
|
source = super._initializeSource(source, options);
|
||||||
source.currency.coins = {
|
for (const type of ['coins', 'handfuls', 'bags', 'chests']) {
|
||||||
enabled: source.currency.coins.enabled ?? true,
|
const initial = this.schema.fields.currency.fields[type].getInitialValue();
|
||||||
label: source.currency.coins.label || source.currency.coins
|
source.currency[type] = foundry.utils.mergeObject(initial, source.currency[type], { inplace: false });
|
||||||
};
|
}
|
||||||
source.currency.handfuls = {
|
|
||||||
enabled: source.currency.handfuls.enabled ?? true,
|
|
||||||
label: source.currency.handfuls.label || source.currency.handfuls
|
|
||||||
};
|
|
||||||
source.currency.bags = {
|
|
||||||
enabled: source.currency.bags.enabled ?? true,
|
|
||||||
label: source.currency.bags.label || source.currency.bags
|
|
||||||
};
|
|
||||||
source.currency.chests = {
|
|
||||||
enabled: source.currency.chests.enabled ?? true,
|
|
||||||
label: source.currency.chests.label || source.currency.chests
|
|
||||||
};
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,13 @@ export default class DhVariantRules extends foundry.abstract.DataModel {
|
||||||
label: 'DAGGERHEART.CONFIG.Range.close.name'
|
label: 'DAGGERHEART.CONFIG.Range.close.name'
|
||||||
}),
|
}),
|
||||||
far: new fields.NumberField({ required: true, initial: 60, label: 'DAGGERHEART.CONFIG.Range.far.name' })
|
far: new fields.NumberField({ required: true, initial: 60, label: 'DAGGERHEART.CONFIG.Range.far.name' })
|
||||||
|
}),
|
||||||
|
massiveDamage: new fields.SchemaField({
|
||||||
|
enabled: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.VariantRules.FIELDS.massiveDamage.enabled.label'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,10 +98,10 @@ export default class D20Roll extends DHRoll {
|
||||||
this.options.roll.modifiers = this.applyBaseBonus();
|
this.options.roll.modifiers = this.applyBaseBonus();
|
||||||
|
|
||||||
this.options.experiences?.forEach(m => {
|
this.options.experiences?.forEach(m => {
|
||||||
if (this.options.data.experiences?.[m])
|
if (this.options.data.system?.experiences?.[m])
|
||||||
this.options.roll.modifiers.push({
|
this.options.roll.modifiers.push({
|
||||||
label: this.options.data.experiences[m].name,
|
label: this.options.data.system.experiences[m].name,
|
||||||
value: this.options.data.experiences[m].value
|
value: this.options.data.system.experiences[m].value
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -236,67 +236,3 @@ export default class DHRoll extends Roll {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerRollDiceHooks = () => {
|
|
||||||
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
|
|
||||||
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
|
||||||
if (
|
|
||||||
automationSettings.countdownAutomation &&
|
|
||||||
config.actionType !== CONFIG.DH.ITEM.actionTypes.reaction.id &&
|
|
||||||
!config.tagTeamSelected &&
|
|
||||||
!config.skips?.updateCountdowns
|
|
||||||
) {
|
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
|
|
||||||
|
|
||||||
if (config.roll.result.duality === -1) {
|
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hopeFearAutomation = automationSettings.hopeFear;
|
|
||||||
if (
|
|
||||||
!config.source?.actor ||
|
|
||||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
|
||||||
config.actionType === 'reaction' ||
|
|
||||||
config.tagTeamSelected ||
|
|
||||||
config.skips?.resources
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
const actor = await fromUuid(config.source.actor);
|
|
||||||
let updates = [];
|
|
||||||
if (!actor) return;
|
|
||||||
if (config.roll.isCritical || config.roll.result.duality === 1)
|
|
||||||
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
|
|
||||||
if (config.roll.isCritical) updates.push({ key: 'stress', value: 1, total: -1, enabled: true });
|
|
||||||
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
|
|
||||||
|
|
||||||
if (config.rerolledRoll) {
|
|
||||||
if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
|
|
||||||
updates.push({ key: 'hope', value: -1, total: 1, enabled: true });
|
|
||||||
if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
|
||||||
if (config.rerolledRoll.result.duality === -1)
|
|
||||||
updates.push({ key: 'fear', value: -1, total: 1, enabled: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updates.length) {
|
|
||||||
const target = actor.system.partner ?? actor;
|
|
||||||
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
|
|
||||||
if (config.rerolledRoll) target.modifyResource(updates);
|
|
||||||
else config.costs = [...(config.costs ?? []), ...updates];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
|
|
||||||
|
|
||||||
const rollResult = config.roll.success || config.targets.some(t => t.hit),
|
|
||||||
looseSpotlight = !rollResult || config.roll.result.duality === -1;
|
|
||||||
|
|
||||||
if (looseSpotlight && game.combat?.active) {
|
|
||||||
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
|
|
||||||
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||||
import D20Roll from './d20Roll.mjs';
|
import D20Roll from './d20Roll.mjs';
|
||||||
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||||
|
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||||
|
|
||||||
export default class DualityRoll extends D20Roll {
|
export default class DualityRoll extends D20Roll {
|
||||||
_advantageFaces = 6;
|
_advantageFaces = 6;
|
||||||
|
|
@ -19,7 +20,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
return game.i18n.localize(
|
return game.i18n.localize(
|
||||||
`DAGGERHEART.GENERAL.${this.options?.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
|
`DAGGERHEART.GENERAL.${this.options?.actionType === 'reaction' ? 'reactionRoll' : 'dualityRoll'}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,6 +220,88 @@ export default class DualityRoll extends D20Roll {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async buildPost(roll, config, message) {
|
||||||
|
await super.buildPost(roll, config, message);
|
||||||
|
|
||||||
|
await DualityRoll.dualityUpdate(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addDualityResourceUpdates(config) {
|
||||||
|
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
|
const hopeFearAutomation = automationSettings.hopeFear;
|
||||||
|
if (
|
||||||
|
!config.source?.actor ||
|
||||||
|
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||||
|
config.actionType === 'reaction' ||
|
||||||
|
config.tagTeamSelected ||
|
||||||
|
config.skips?.resources
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
const actor = await fromUuid(config.source.actor);
|
||||||
|
let updates = [];
|
||||||
|
if (!actor) return;
|
||||||
|
|
||||||
|
if (config.rerolledRoll) {
|
||||||
|
if (config.roll.result.duality != config.rerolledRoll.result.duality) {
|
||||||
|
const hope =
|
||||||
|
(config.roll.isCritical || config.roll.result.duality === 1 ? 1 : 0) -
|
||||||
|
(config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1 ? 1 : 0);
|
||||||
|
const stress = (config.roll.isCritical ? 1 : 0) - (config.rerolledRoll.isCritical ? 1 : 0);
|
||||||
|
const fear =
|
||||||
|
(config.roll.result.duality === -1 ? 1 : 0) - (config.rerolledRoll.result.duality === -1 ? 1 : 0);
|
||||||
|
|
||||||
|
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
|
||||||
|
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
|
||||||
|
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (config.roll.isCritical || config.roll.result.duality === 1)
|
||||||
|
updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
|
||||||
|
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||||
|
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates.length) {
|
||||||
|
// const target = actor.system.partner ?? actor;
|
||||||
|
if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
|
||||||
|
config.resourceUpdates.addResources(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async dualityUpdate(config) {
|
||||||
|
const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||||
|
if (
|
||||||
|
automationSettings.countdownAutomation &&
|
||||||
|
config.actionType !== 'reaction' &&
|
||||||
|
!config.tagTeamSelected &&
|
||||||
|
!config.skips?.updateCountdowns
|
||||||
|
) {
|
||||||
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
|
|
||||||
|
if (config.roll.result.duality === -1) {
|
||||||
|
await updateCountdowns(
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id,
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await DualityRoll.addDualityResourceUpdates(config);
|
||||||
|
|
||||||
|
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
|
||||||
|
|
||||||
|
const rollResult = config.roll.success || config.targets?.some(t => t.hit),
|
||||||
|
looseSpotlight = !rollResult || config.roll.result.duality === -1;
|
||||||
|
|
||||||
|
if (looseSpotlight && game.combat?.active) {
|
||||||
|
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
|
||||||
|
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async reroll(rollString, target, message) {
|
static async reroll(rollString, target, message) {
|
||||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
|
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
|
||||||
const term = parsedRoll.terms[target.dataset.dieIndex];
|
const term = parsedRoll.terms[target.dataset.dieIndex];
|
||||||
|
|
@ -257,14 +340,20 @@ export default class DualityRoll extends D20Roll {
|
||||||
newRoll.extra = newRoll.extra.slice(2);
|
newRoll.extra = newRoll.extra.slice(2);
|
||||||
|
|
||||||
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
Hooks.call(`${CONFIG.DH.id}.postRollDuality`, {
|
|
||||||
|
const actor = message.system.source.actor ? await foundry.utils.fromUuid(message.system.source.actor) : null;
|
||||||
|
const config = {
|
||||||
source: { actor: message.system.source.actor ?? '' },
|
source: { actor: message.system.source.actor ?? '' },
|
||||||
targets: message.system.targets,
|
targets: message.system.targets,
|
||||||
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
|
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
|
||||||
roll: newRoll,
|
roll: newRoll,
|
||||||
rerolledRoll:
|
rerolledRoll: message.system.roll,
|
||||||
newRoll.result.duality !== message.system.roll.result.duality ? message.system.roll : undefined
|
resourceUpdates: new ResourceUpdateMap(actor)
|
||||||
});
|
};
|
||||||
|
|
||||||
|
await DualityRoll.addDualityResourceUpdates(config);
|
||||||
|
await config.resourceUpdates.updateResources();
|
||||||
|
|
||||||
return { newRoll, parsedRoll };
|
return { newRoll, parsedRoll };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
export { default as DhpActor } from './actor.mjs';
|
export { default as DhpActor } from './actor.mjs';
|
||||||
export { default as DHItem } from './item.mjs';
|
export { default as DHItem } from './item.mjs';
|
||||||
export { default as DhpCombat } from './combat.mjs';
|
export { default as DhpCombat } from './combat.mjs';
|
||||||
|
export { default as DHCombatant } from './combatant.mjs';
|
||||||
export { default as DhActiveEffect } from './activeEffect.mjs';
|
export { default as DhActiveEffect } from './activeEffect.mjs';
|
||||||
export { default as DhChatMessage } from './chatMessage.mjs';
|
export { default as DhChatMessage } from './chatMessage.mjs';
|
||||||
|
export { default as DhScene } from './scene.mjs';
|
||||||
export { default as DhToken } from './token.mjs';
|
export { default as DhToken } from './token.mjs';
|
||||||
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
||||||
export { default as DhTemplateManager } from './templateManager.mjs';
|
export { default as DhTemplateManager } from './templateManager.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { itemAbleRollParse } from '../helpers/utils.mjs';
|
import { itemAbleRollParse } from '../helpers/utils.mjs';
|
||||||
|
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -85,6 +86,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
await super._preCreate(data, options, user);
|
await super._preCreate(data, options, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_onCreate(data, options, userId) {
|
||||||
|
super._onCreate(data, options, userId);
|
||||||
|
|
||||||
|
Hooks.callAll(RefreshType.EffectsDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
_onDelete(data, options, userId) {
|
||||||
|
super._onDelete(data, options, userId);
|
||||||
|
|
||||||
|
Hooks.callAll(RefreshType.EffectsDisplay);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* Methods */
|
/* Methods */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -179,27 +194,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareDerivedData() {
|
prepareDerivedData() {
|
||||||
/* Preventing subclass features from transferring to actor if they do not have the right subclass advancement */
|
/* Check for item availability such as in the case of subclass advancement. */
|
||||||
if (this.parent?.type === 'feature') {
|
if (this.parent?.parent?.system?.isItemAvailable) {
|
||||||
const origSubclassParent = this.parent.system.originItemType === 'subclass';
|
if (!this.parent.parent.system.isItemAvailable(this.parent)) {
|
||||||
if (origSubclassParent) {
|
this.transfer = false;
|
||||||
const subclass = this.parent.parent.items.find(
|
|
||||||
x =>
|
|
||||||
x.type === 'subclass' &&
|
|
||||||
x.system.isMulticlass === (this.parent.system.identifier === 'multiclass')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (subclass) {
|
|
||||||
const featureState = subclass.system.featureState;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
|
|
||||||
featureState < 2) ||
|
|
||||||
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
|
|
||||||
) {
|
|
||||||
this.transfer = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||||
import { LevelOptionType } from '../data/levelTier.mjs';
|
import { LevelOptionType } from '../data/levelTier.mjs';
|
||||||
import DHFeature from '../data/item/feature.mjs';
|
import DHFeature from '../data/item/feature.mjs';
|
||||||
import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs';
|
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
|
||||||
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
||||||
|
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||||
|
|
||||||
export default class DhpActor extends Actor {
|
export default class DhpActor extends Actor {
|
||||||
parties = new Set();
|
parties = new Set();
|
||||||
|
|
@ -73,16 +74,27 @@ export default class DhpActor extends Actor {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
async _preCreate(data, options, user) {
|
async _preCreate(data, options, user) {
|
||||||
if ((await super._preCreate(data, options, user)) === false) return false;
|
if ((await super._preCreate(data, options, user)) === false) return false;
|
||||||
|
const update = {};
|
||||||
|
|
||||||
|
// Set default token size. Done here as we do not want to set a datamodel default, since that would apply the sizing to third party actor modules that aren't set up with the size system.
|
||||||
|
if (this.system.metadata.usesSize && !data.system?.size) {
|
||||||
|
Object.assign(update, {
|
||||||
|
system: {
|
||||||
|
size: CONFIG.DH.ACTOR.tokenSize.medium.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Configure prototype token settings
|
// Configure prototype token settings
|
||||||
const prototypeToken = {};
|
|
||||||
if (['character', 'companion', 'party'].includes(this.type))
|
if (['character', 'companion', 'party'].includes(this.type))
|
||||||
Object.assign(prototypeToken, {
|
Object.assign(update, {
|
||||||
sight: { enabled: true },
|
prototypeToken: {
|
||||||
actorLink: true,
|
sight: { enabled: true },
|
||||||
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
actorLink: true,
|
||||||
|
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.updateSource({ prototypeToken });
|
this.updateSource(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate(changes, options, userId) {
|
_onUpdate(changes, options, userId) {
|
||||||
|
|
@ -477,6 +489,7 @@ export default class DhpActor extends Actor {
|
||||||
async diceRoll(config) {
|
async diceRoll(config) {
|
||||||
config.source = { ...(config.source ?? {}), actor: this.uuid };
|
config.source = { ...(config.source ?? {}), actor: this.uuid };
|
||||||
config.data = this.getRollData();
|
config.data = this.getRollData();
|
||||||
|
config.resourceUpdates = new ResourceUpdateMap(this);
|
||||||
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
|
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
|
||||||
return await rollClass.build(config);
|
return await rollClass.build(config);
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +539,11 @@ export default class DhpActor extends Actor {
|
||||||
|
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
getRollData() {
|
getRollData() {
|
||||||
const rollData = super.getRollData();
|
const rollData = foundry.utils.deepClone(super.getRollData());
|
||||||
|
/* system gets repeated infinately which causes issues when trying to use the data for document creation */
|
||||||
|
delete rollData.system;
|
||||||
|
|
||||||
|
rollData.id = this.id;
|
||||||
rollData.name = this.name;
|
rollData.name = this.name;
|
||||||
rollData.system = this.system.getRollData();
|
rollData.system = this.system.getRollData();
|
||||||
rollData.prof = this.system.proficiency ?? 1;
|
rollData.prof = this.system.proficiency ?? 1;
|
||||||
|
|
@ -614,6 +631,19 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.type === 'adversary') {
|
||||||
|
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
|
||||||
|
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
|
||||||
|
}, 0);
|
||||||
|
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
|
||||||
|
|
||||||
|
if (
|
||||||
|
hpDamage.value &&
|
||||||
|
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
|
||||||
|
) {
|
||||||
|
hpDamage.value -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updates.forEach(
|
updates.forEach(
|
||||||
|
|
@ -679,6 +709,10 @@ export default class DhpActor extends Actor {
|
||||||
return updates;
|
return updates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources are modified asynchronously, so be careful not to update the same resource in
|
||||||
|
* quick succession.
|
||||||
|
*/
|
||||||
async modifyResource(resources) {
|
async modifyResource(resources) {
|
||||||
if (!resources?.length) return;
|
if (!resources?.length) return;
|
||||||
|
|
||||||
|
|
@ -761,6 +795,11 @@ export default class DhpActor extends Actor {
|
||||||
}
|
}
|
||||||
|
|
||||||
convertDamageToThreshold(damage) {
|
convertDamageToThreshold(damage) {
|
||||||
|
const massiveDamageEnabled = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules)
|
||||||
|
.massiveDamage.enabled;
|
||||||
|
if (massiveDamageEnabled && damage >= this.system.damageThresholds.severe * 2) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;
|
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -844,4 +883,65 @@ export default class DhpActor extends Actor {
|
||||||
if (this.system._getTags) tags.push(...this.system._getTags());
|
if (this.system._getTags) tags.push(...this.system._getTags());
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Get active effects */
|
||||||
|
getActiveEffects() {
|
||||||
|
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||||
|
return this.effects
|
||||||
|
.filter(x => !x.disabled)
|
||||||
|
.reduce((acc, effect) => {
|
||||||
|
acc.push(effect);
|
||||||
|
|
||||||
|
const currentStatusActiveEffects = acc.filter(
|
||||||
|
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var status of effect.statuses) {
|
||||||
|
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
|
||||||
|
const statusData = statusMap.get(status);
|
||||||
|
if (statusData) {
|
||||||
|
acc.push({
|
||||||
|
condition: status,
|
||||||
|
appliedBy: game.i18n.localize(effect.name),
|
||||||
|
name: game.i18n.localize(statusData.name),
|
||||||
|
statuses: new Set([status]),
|
||||||
|
img: statusData.icon ?? statusData.img,
|
||||||
|
description: game.i18n.localize(statusData.description),
|
||||||
|
tint: effect.tint
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Temporarily copying the foundry method to add a fix to a bug with scenes
|
||||||
|
https://discord.com/channels/170995199584108546/1296292044011995136/1446693077443149856
|
||||||
|
*/
|
||||||
|
getDependentTokens({ scenes, linked = false } = {}) {
|
||||||
|
if (this.isToken && !scenes) return [this.token];
|
||||||
|
if (scenes) scenes = Array.isArray(scenes) ? scenes : [scenes];
|
||||||
|
else scenes = Array.from(this._dependentTokens.keys());
|
||||||
|
|
||||||
|
/* Code to filter out nonexistant scenes */
|
||||||
|
scenes = scenes.filter(scene => game.scenes.some(x => x.id === scene.id));
|
||||||
|
|
||||||
|
if (this.isToken) {
|
||||||
|
const parent = this.token.parent;
|
||||||
|
return scenes.includes(parent) ? [this.token] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const allTokens = [];
|
||||||
|
for (const scene of scenes) {
|
||||||
|
if (!scene) continue;
|
||||||
|
const tokens = this._dependentTokens.get(scene);
|
||||||
|
for (const token of tokens ?? []) {
|
||||||
|
if (!linked || token.actorLink) allTokens.push(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allTokens;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,4 +16,54 @@ export default class DhpCombat extends Combat {
|
||||||
|
|
||||||
return a.name.localeCompare(b.name);
|
return a.name.localeCompare(b.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleModifierEffects(add, actors, category, groupingKey) {
|
||||||
|
const effectData = category && groupingKey ? [{ category, grouping: groupingKey }] : this.system.battleToggles;
|
||||||
|
if (add) {
|
||||||
|
const effects = effectData.reduce((acc, toggle) => {
|
||||||
|
const grouping = CONFIG.DH.ENCOUNTER.BPModifiers[toggle.category]?.[toggle.grouping];
|
||||||
|
if (!grouping?.effects?.length) return acc;
|
||||||
|
acc.push(
|
||||||
|
...grouping.effects.map(effect => ({
|
||||||
|
...effect,
|
||||||
|
name: game.i18n.localize(effect.name),
|
||||||
|
description: game.i18n.localize(effect.description),
|
||||||
|
effectTargetTypes: grouping.effectTargetTypes ?? [],
|
||||||
|
flags: {
|
||||||
|
[`${CONFIG.DH.id}.${CONFIG.DH.FLAGS.combatToggle}`]: {
|
||||||
|
category: toggle.category,
|
||||||
|
grouping: toggle.grouping
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!effects.length) return;
|
||||||
|
|
||||||
|
for (let actor of actors) {
|
||||||
|
await actor.createEmbeddedDocuments(
|
||||||
|
'ActiveEffect',
|
||||||
|
effects.filter(x => x.effectTargetTypes.includes(actor.type))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let actor of actors) {
|
||||||
|
await actor.deleteEmbeddedDocuments(
|
||||||
|
'ActiveEffect',
|
||||||
|
actor.effects
|
||||||
|
.filter(x => {
|
||||||
|
const flag = x.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.combatToggle);
|
||||||
|
if (!flag) return false;
|
||||||
|
return effectData.some(
|
||||||
|
data => flag.category == data.category && flag.grouping === data.grouping
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map(x => x.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
6
module/documents/combatant.mjs
Normal file
6
module/documents/combatant.mjs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default class DhCombatant extends Combatant {
|
||||||
|
/**@inheritdoc */
|
||||||
|
get isNPC() {
|
||||||
|
return this.actor?.isNPC ?? (!this.actor || !this.hasPlayerOwner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,13 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async createDocuments(sources, operation) {
|
||||||
|
// Ensure that items being created are valid to the actor its being added to
|
||||||
|
const actor = operation.parent;
|
||||||
|
sources = actor?.system?.isItemValid ? sources.filter((s) => actor.system.isItemValid(s)) : sources;
|
||||||
|
return super.createDocuments(sources, operation);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
|
||||||
40
module/documents/scene.mjs
Normal file
40
module/documents/scene.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import DHToken from './token.mjs';
|
||||||
|
|
||||||
|
export default class DhScene extends Scene {
|
||||||
|
/** A map of `TokenDocument` IDs embedded in this scene long with new dimensions from actor size-category changes */
|
||||||
|
#sizeSyncBatch = new Map();
|
||||||
|
|
||||||
|
/** Synchronize a token's dimensions with its actor's size category. */
|
||||||
|
syncTokenDimensions(tokenDoc, tokenSize) {
|
||||||
|
if (!tokenDoc.parent?.tokens.has(tokenDoc.id)) return;
|
||||||
|
const prototype = tokenDoc.actor?.prototypeToken ?? tokenDoc;
|
||||||
|
this.#sizeSyncBatch.set(tokenDoc.id, {
|
||||||
|
size: tokenSize,
|
||||||
|
prototypeSize: { width: prototype.width, height: prototype.height },
|
||||||
|
position: { x: tokenDoc.x, y: tokenDoc.y, elevation: tokenDoc.elevation }
|
||||||
|
});
|
||||||
|
this.#processSyncBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieve size and clear size-sync batch, make updates. */
|
||||||
|
#processSyncBatch = foundry.utils.debounce(() => {
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const entries = this.#sizeSyncBatch
|
||||||
|
.entries()
|
||||||
|
.toArray()
|
||||||
|
.map(([_id, { size, prototypeSize, position }]) => {
|
||||||
|
const tokenSize = tokenSizes[size];
|
||||||
|
const width = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.width;
|
||||||
|
const height = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.height;
|
||||||
|
const updatedPosition = DHToken.getSnappedPositionInSquareGrid(this.grid, position, width, height);
|
||||||
|
return {
|
||||||
|
_id,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...updatedPosition
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.#sizeSyncBatch.clear();
|
||||||
|
this.updateEmbeddedDocuments('Token', entries, { animation: { movementSpeed: 1.5 } });
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export default class DHToken extends TokenDocument {
|
export default class DHToken extends CONFIG.Token.documentClass {
|
||||||
/**
|
/**
|
||||||
* Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar.
|
* Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar.
|
||||||
* @param {object} attributes The tracked attributes which can be chosen from
|
* @param {object} attributes The tracked attributes which can be chosen from
|
||||||
|
|
@ -72,8 +72,468 @@ export default class DHToken extends TokenDocument {
|
||||||
}
|
}
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
_shouldRecordMovementHistory() {
|
_shouldRecordMovementHistory() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
static async createCombatants(tokens, combat) {
|
||||||
|
combat ??= game.combats.viewed;
|
||||||
|
if (combat?.system?.battleToggles?.length) {
|
||||||
|
await combat.toggleModifierEffects(
|
||||||
|
true,
|
||||||
|
tokens.filter(x => x.actor).map(x => x.actor)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
super.createCombatants(tokens, combat ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
static async deleteCombatants(tokens, { combat } = {}) {
|
||||||
|
combat ??= game.combats.viewed;
|
||||||
|
if (combat?.system?.battleToggles?.length) {
|
||||||
|
await combat.toggleModifierEffects(
|
||||||
|
false,
|
||||||
|
tokens.filter(x => x.actor).map(x => x.actor)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
super.deleteCombatants(tokens, combat ?? {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
static async _preCreateOperation(documents, operation, user) {
|
||||||
|
const allowed = await super._preCreateOperation(documents, operation, user);
|
||||||
|
if (allowed === false) return false;
|
||||||
|
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
for (const document of documents) {
|
||||||
|
const actor = document.actor;
|
||||||
|
if (actor?.system.metadata.usesSize) {
|
||||||
|
const tokenSize = tokenSizes[actor.system.size];
|
||||||
|
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||||
|
document.updateSource({
|
||||||
|
width: tokenSize,
|
||||||
|
height: tokenSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
_onRelatedUpdate(update = {}, operation = {}) {
|
||||||
|
super._onRelatedUpdate(update, operation);
|
||||||
|
|
||||||
|
if (!this.actor?.isOwner) return;
|
||||||
|
|
||||||
|
const updates = Array.isArray(update) ? update : [update];
|
||||||
|
const activeGM = game.users.activeGM; // Let the active GM take care of updates if available
|
||||||
|
for (let update of updates) {
|
||||||
|
if (
|
||||||
|
this.actor.system.metadata.usesSize &&
|
||||||
|
update.system?.size &&
|
||||||
|
activeGM &&
|
||||||
|
game.user.id === activeGM.id
|
||||||
|
) {
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const tokenSize = tokenSizes[update.system.size];
|
||||||
|
if (tokenSize !== this.width || tokenSize !== this.height) {
|
||||||
|
this.parent?.syncTokenDimensions(this, update.system.size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
getSnappedPosition(data = {}) {
|
||||||
|
const grid = this.parent?.grid ?? BaseScene.defaultGrid;
|
||||||
|
const x = data.x ?? this.x;
|
||||||
|
const y = data.y ?? this.y;
|
||||||
|
let elevation = data.elevation ?? this.elevation;
|
||||||
|
const unsnapped = { x, y, elevation };
|
||||||
|
|
||||||
|
// Gridless grid
|
||||||
|
if (grid.isGridless) return unsnapped;
|
||||||
|
|
||||||
|
// Get position and elevation
|
||||||
|
elevation = Math.round(elevation / grid.distance) * grid.distance;
|
||||||
|
|
||||||
|
let width = data.width ?? this.width;
|
||||||
|
let height = data.height ?? this.height;
|
||||||
|
|
||||||
|
if (this.actor?.system.metadata.usesSize) {
|
||||||
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
|
const tokenSize = tokenSizes[this.actor.system.size];
|
||||||
|
if (tokenSize && this.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||||
|
width = tokenSize ?? width;
|
||||||
|
height = tokenSize ?? height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round width and height to nearest multiple of 0.5 if not small
|
||||||
|
width = width < 1 ? width : Math.round(width * 2) / 2;
|
||||||
|
height = height < 1 ? height : Math.round(height * 2) / 2;
|
||||||
|
const shape = data.shape ?? this.shape;
|
||||||
|
|
||||||
|
// Square grid
|
||||||
|
let snapped;
|
||||||
|
if (grid.isSquare) snapped = DHToken.getSnappedPositionInSquareGrid(grid, unsnapped, width, height);
|
||||||
|
// Hexagonal grid
|
||||||
|
else snapped = DHToken.getSnappedPositionInHexagonalGrid(grid, unsnapped, width, height, shape);
|
||||||
|
return { x: snapped.x, y: snapped.y, elevation };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSnappedPositionInSquareGrid(grid, position, width, height) {
|
||||||
|
const M = CONST.GRID_SNAPPING_MODES;
|
||||||
|
// Small tokens snap to any vertex of the subgrid with resolution 4
|
||||||
|
// where the token is fully contained within the grid space
|
||||||
|
const isTiny = (width === 0.5 && height <= 1) || (width <= 1 && height === 0.5);
|
||||||
|
if (isTiny) {
|
||||||
|
let x = position.x / grid.size;
|
||||||
|
let y = position.y / grid.size;
|
||||||
|
if (width === 1) x = Math.round(x);
|
||||||
|
else {
|
||||||
|
x = Math.floor(x * 8);
|
||||||
|
const k = ((x % 8) + 8) % 8;
|
||||||
|
if (k >= 6) x = Math.ceil(x / 8);
|
||||||
|
else if (k === 5) x = Math.floor(x / 8) + 0.5;
|
||||||
|
else x = Math.round(x / 2) / 4;
|
||||||
|
}
|
||||||
|
if (height === 1) y = Math.round(y);
|
||||||
|
else {
|
||||||
|
y = Math.floor(y * 8);
|
||||||
|
const k = ((y % 8) + 8) % 8;
|
||||||
|
if (k >= 6) y = Math.ceil(y / 8);
|
||||||
|
else if (k === 5) y = Math.floor(y / 8) + 0.5;
|
||||||
|
else y = Math.round(y / 2) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
x *= grid.size;
|
||||||
|
y *= grid.size;
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
} else if (width < 1 && height < 1) {
|
||||||
|
// isSmall
|
||||||
|
let xGrid = Math.round(position.x / grid.size);
|
||||||
|
let yGrid = Math.round(position.y / grid.size);
|
||||||
|
|
||||||
|
const x = xGrid * grid.size + grid.size / 2 - (width * grid.size) / 2;
|
||||||
|
const y = yGrid * grid.size + grid.size / 2 - (height * grid.size) / 2;
|
||||||
|
|
||||||
|
return { x, y };
|
||||||
|
}
|
||||||
|
|
||||||
|
const modeX = Number.isInteger(width) ? M.VERTEX : M.VERTEX | M.EDGE_MIDPOINT | M.CENTER;
|
||||||
|
const modeY = Number.isInteger(height) ? M.VERTEX : M.VERTEX | M.EDGE_MIDPOINT | M.CENTER;
|
||||||
|
|
||||||
|
if (modeX === modeY) return grid.getSnappedPoint(position, { mode: modeX });
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: grid.getSnappedPoint(position, { mode: modeX }).x,
|
||||||
|
y: grid.getSnappedPoint(position, { mode: modeY }).y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region CopyPasta for mean private methods that have to be duplicated
|
||||||
|
static getSnappedPositionInHexagonalGrid(grid, position, width, height, shape) {
|
||||||
|
// Hexagonal shape
|
||||||
|
const hexagonalShape = DHToken.#getHexagonalShape(width, height, shape, grid.columns);
|
||||||
|
if (hexagonalShape) {
|
||||||
|
const offsetX = hexagonalShape.anchor.x * grid.sizeX;
|
||||||
|
const offsetY = hexagonalShape.anchor.y * grid.sizeY;
|
||||||
|
position = grid.getCenterPoint({ x: position.x + offsetX, y: position.y + offsetY });
|
||||||
|
position.x -= offsetX;
|
||||||
|
position.y -= offsetY;
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rectagular shape
|
||||||
|
const M = CONST.GRID_SNAPPING_MODES;
|
||||||
|
return grid.getSnappedPoint(position, { mode: M.CENTER | M.VERTEX | M.CORNER | M.SIDE_MIDPOINT });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cache of hexagonal shapes.
|
||||||
|
* @type {Map<string, DeepReadonly<TokenHexagonalShapeData>>}
|
||||||
|
*/
|
||||||
|
static #hexagonalShapes = new Map();
|
||||||
|
|
||||||
|
static #getHexagonalShape(width, height, shape, columns) {
|
||||||
|
if (!Number.isInteger(width * 2) || !Number.isInteger(height * 2)) return null;
|
||||||
|
|
||||||
|
// TODO: can we set a max of 2^13 on width and height so that we may use an integer key?
|
||||||
|
const key = `${width},${height},${shape}${columns ? 'C' : 'R'}`;
|
||||||
|
let data = DHToken.#hexagonalShapes.get(key);
|
||||||
|
if (data) return data;
|
||||||
|
|
||||||
|
// Hexagon symmetry
|
||||||
|
if (columns) {
|
||||||
|
const rowData = BaseToken.#getHexagonalShape(height, width, shape, false);
|
||||||
|
if (!rowData) return null;
|
||||||
|
|
||||||
|
// Transpose the offsets/points of the shape in row orientation
|
||||||
|
const offsets = { even: [], odd: [] };
|
||||||
|
for (const { i, j } of rowData.offsets.even) offsets.even.push({ i: j, j: i });
|
||||||
|
for (const { i, j } of rowData.offsets.odd) offsets.odd.push({ i: j, j: i });
|
||||||
|
offsets.even.sort(({ i: i0, j: j0 }, { i: i1, j: j1 }) => j0 - j1 || i0 - i1);
|
||||||
|
offsets.odd.sort(({ i: i0, j: j0 }, { i: i1, j: j1 }) => j0 - j1 || i0 - i1);
|
||||||
|
const points = [];
|
||||||
|
for (let i = rowData.points.length; i > 0; i -= 2) {
|
||||||
|
points.push(rowData.points[i - 1], rowData.points[i - 2]);
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
offsets,
|
||||||
|
points,
|
||||||
|
center: { x: rowData.center.y, y: rowData.center.x },
|
||||||
|
anchor: { x: rowData.anchor.y, y: rowData.anchor.x }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small hexagon
|
||||||
|
else if (width === 0.5 && height === 0.5) {
|
||||||
|
data = {
|
||||||
|
offsets: { even: [{ i: 0, j: 0 }], odd: [{ i: 0, j: 0 }] },
|
||||||
|
points: [0.25, 0.0, 0.5, 0.125, 0.5, 0.375, 0.25, 0.5, 0.0, 0.375, 0.0, 0.125],
|
||||||
|
center: { x: 0.25, y: 0.25 },
|
||||||
|
anchor: { x: 0.25, y: 0.25 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal hexagon
|
||||||
|
else if (width === 1 && height === 1) {
|
||||||
|
data = {
|
||||||
|
offsets: { even: [{ i: 0, j: 0 }], odd: [{ i: 0, j: 0 }] },
|
||||||
|
points: [0.5, 0.0, 1.0, 0.25, 1, 0.75, 0.5, 1.0, 0.0, 0.75, 0.0, 0.25],
|
||||||
|
center: { x: 0.5, y: 0.5 },
|
||||||
|
anchor: { x: 0.5, y: 0.5 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexagonal ellipse or trapezoid
|
||||||
|
else if (shape <= CONST.TOKEN_SHAPES.TRAPEZOID_2) {
|
||||||
|
data = DHToken.#createHexagonalEllipseOrTrapezoid(width, height, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexagonal rectangle
|
||||||
|
else if (shape <= CONST.TOKEN_SHAPES.RECTANGLE_2) {
|
||||||
|
data = DHToken.#createHexagonalRectangle(width, height, shape);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the shape
|
||||||
|
if (data) {
|
||||||
|
foundry.utils.deepFreeze(data);
|
||||||
|
DHToken.#hexagonalShapes.set(key, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static #createHexagonalEllipseOrTrapezoid(width, height, shape) {
|
||||||
|
if (!Number.isInteger(width) || !Number.isInteger(height)) return null;
|
||||||
|
const points = [];
|
||||||
|
let top;
|
||||||
|
let bottom;
|
||||||
|
switch (shape) {
|
||||||
|
case CONST.TOKEN_SHAPES.ELLIPSE_1:
|
||||||
|
if (height >= 2 * width) return null;
|
||||||
|
top = Math.floor(height / 2);
|
||||||
|
bottom = Math.floor((height - 1) / 2);
|
||||||
|
break;
|
||||||
|
case CONST.TOKEN_SHAPES.ELLIPSE_2:
|
||||||
|
if (height >= 2 * width) return null;
|
||||||
|
top = Math.floor((height - 1) / 2);
|
||||||
|
bottom = Math.floor(height / 2);
|
||||||
|
break;
|
||||||
|
case CONST.TOKEN_SHAPES.TRAPEZOID_1:
|
||||||
|
if (height > width) return null;
|
||||||
|
top = height - 1;
|
||||||
|
bottom = 0;
|
||||||
|
break;
|
||||||
|
case CONST.TOKEN_SHAPES.TRAPEZOID_2:
|
||||||
|
if (height > width) return null;
|
||||||
|
top = 0;
|
||||||
|
bottom = height - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const offsets = { even: [], odd: [] };
|
||||||
|
for (let i = bottom; i > 0; i--) {
|
||||||
|
for (let j = 0; j < width - i; j++) {
|
||||||
|
offsets.even.push({ i: bottom - i, j: j + (((bottom & 1) + i + 1) >> 1) });
|
||||||
|
offsets.odd.push({ i: bottom - i, j: j + (((bottom & 1) + i) >> 1) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i <= top; i++) {
|
||||||
|
for (let j = 0; j < width - i; j++) {
|
||||||
|
offsets.even.push({ i: bottom + i, j: j + (((bottom & 1) + i + 1) >> 1) });
|
||||||
|
offsets.odd.push({ i: bottom + i, j: j + (((bottom & 1) + i) >> 1) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let x = 0.5 * bottom;
|
||||||
|
let y = 0.25;
|
||||||
|
for (let k = width - bottom; k--; ) {
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
}
|
||||||
|
points.push(x, y);
|
||||||
|
for (let k = bottom; k--; ) {
|
||||||
|
y += 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
y += 0.5;
|
||||||
|
for (let k = top; k--; ) {
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
y += 0.5;
|
||||||
|
}
|
||||||
|
for (let k = width - top; k--; ) {
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
}
|
||||||
|
points.push(x, y);
|
||||||
|
for (let k = top; k--; ) {
|
||||||
|
y -= 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
y -= 0.5;
|
||||||
|
for (let k = bottom; k--; ) {
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
y -= 0.5;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
offsets,
|
||||||
|
points,
|
||||||
|
// We use the centroid of the polygon for ellipse and trapzoid shapes
|
||||||
|
center: foundry.utils.polygonCentroid(points),
|
||||||
|
anchor: bottom % 2 ? { x: 0.0, y: 0.5 } : { x: 0.5, y: 0.5 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the row-based hexagonal rectangle given the type, width, and height.
|
||||||
|
* @param {number} width The width of the Token (positive)
|
||||||
|
* @param {number} height The height of the Token (positive)
|
||||||
|
* @param {TokenShapeType} shape The shape type (must be RECTANGLE_1 or RECTANGLE_2)
|
||||||
|
* @returns {TokenHexagonalShapeData|null} The hexagonal shape or null if there is no shape
|
||||||
|
* for the given combination of arguments
|
||||||
|
*/
|
||||||
|
static #createHexagonalRectangle(width, height, shape) {
|
||||||
|
if (width < 1 || !Number.isInteger(height)) return null;
|
||||||
|
if (width === 1 && height > 1) return null;
|
||||||
|
if (!Number.isInteger(width) && height === 1) return null;
|
||||||
|
|
||||||
|
const even = shape === CONST.TOKEN_SHAPES.RECTANGLE_1 || height === 1;
|
||||||
|
const offsets = { even: [], odd: [] };
|
||||||
|
for (let i = 0; i < height; i++) {
|
||||||
|
const j0 = even ? 0 : (i + 1) & 1;
|
||||||
|
const j1 = ((width + (i & 1) * 0.5) | 0) - (even ? i & 1 : 0);
|
||||||
|
for (let j = j0; j < j1; j++) {
|
||||||
|
offsets.even.push({ i, j: j + (i & 1) });
|
||||||
|
offsets.odd.push({ i, j });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let x = even ? 0.0 : 0.5;
|
||||||
|
let y = 0.25;
|
||||||
|
const points = [x, y];
|
||||||
|
while (x + 1 <= width) {
|
||||||
|
x += 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
if (x !== width) {
|
||||||
|
y += 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
while (y + 1.5 <= 0.75 * height) {
|
||||||
|
y += 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
y += 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
if (y + 0.75 < 0.75 * height) {
|
||||||
|
y += 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
y += 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
while (x - 1 >= 0) {
|
||||||
|
x -= 0.5;
|
||||||
|
y += 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
if (x !== 0) {
|
||||||
|
y -= 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
while (y - 1.5 > 0) {
|
||||||
|
y -= 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
y -= 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x -= 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
if (y - 0.75 > 0) {
|
||||||
|
y -= 0.5;
|
||||||
|
points.push(x, y);
|
||||||
|
x += 0.5;
|
||||||
|
y -= 0.25;
|
||||||
|
points.push(x, y);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
offsets,
|
||||||
|
points,
|
||||||
|
// We use center of the rectangle (and not the centroid of the polygon) for the rectangle shapes
|
||||||
|
center: {
|
||||||
|
x: width / 2,
|
||||||
|
y: (0.75 * Math.floor(height) + 0.5 * (height % 1) + 0.25) / 2
|
||||||
|
},
|
||||||
|
anchor: even ? { x: 0.5, y: 0.5 } : { x: 0.0, y: 0.5 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,66 @@
|
||||||
|
import { AdversaryBPPerEncounter, BaseBPPerEncounter } from '../config/encounterConfig.mjs';
|
||||||
|
|
||||||
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
|
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
|
||||||
|
#wide = false;
|
||||||
|
#bordered = false;
|
||||||
|
|
||||||
async activate(element, options = {}) {
|
async activate(element, options = {}) {
|
||||||
const { TextEditor } = foundry.applications.ux;
|
const { TextEditor } = foundry.applications.ux;
|
||||||
|
|
||||||
let html = options.html;
|
let html = options.html;
|
||||||
|
if (element.dataset.tooltip?.startsWith('#battlepoints#')) {
|
||||||
|
this.#wide = true;
|
||||||
|
this.#bordered = true;
|
||||||
|
|
||||||
|
html = await this.getBattlepointHTML(element.dataset.combatId);
|
||||||
|
options.direction = this._determineItemTooltipDirection(element);
|
||||||
|
super.activate(element, { ...options, html: html });
|
||||||
|
|
||||||
|
const lockedTooltip = this.lockTooltip();
|
||||||
|
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
|
||||||
|
element.addEventListener('input', this.toggleModifier.bind(this));
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this.#wide = false;
|
||||||
|
this.#bordered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.dataset.tooltip === '#effect-display#') {
|
||||||
|
this.#bordered = true;
|
||||||
|
let effect = {};
|
||||||
|
if (element.dataset.uuid) {
|
||||||
|
const effectData = (await foundry.utils.fromUuid(element.dataset.uuid)).toObject();
|
||||||
|
effect = {
|
||||||
|
...effectData,
|
||||||
|
name: game.i18n.localize(effectData.name),
|
||||||
|
description: game.i18n.localize(effectData.description ?? effectData.parent.system.description)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||||
|
const condition = conditions[element.dataset.condition];
|
||||||
|
effect = {
|
||||||
|
...condition,
|
||||||
|
name: game.i18n.localize(condition.name),
|
||||||
|
description: game.i18n.localize(condition.description),
|
||||||
|
appliedBy: element.dataset.appliedBy,
|
||||||
|
isLockedCondition: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
html = await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/effect-display.hbs`,
|
||||||
|
{
|
||||||
|
effect
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tooltip.innerHTML = html;
|
||||||
|
options.direction = this._determineItemTooltipDirection(element);
|
||||||
|
} else {
|
||||||
|
this.#bordered = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (element.dataset.tooltip?.startsWith('#item#')) {
|
if (element.dataset.tooltip?.startsWith('#item#')) {
|
||||||
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);
|
||||||
|
|
@ -17,7 +75,8 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
{
|
{
|
||||||
item: item,
|
item: item,
|
||||||
description: item.system?.enrichedDescription ?? item.enrichedDescription,
|
description: item.system?.enrichedDescription ?? item.enrichedDescription,
|
||||||
config: CONFIG.DH
|
config: CONFIG.DH,
|
||||||
|
allDomains: CONFIG.DH.DOMAIN.allDomains()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -175,4 +234,133 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
_setStyle(position = {}) {
|
||||||
|
super._setStyle(position);
|
||||||
|
|
||||||
|
if (this.#wide) {
|
||||||
|
this.tooltip.classList.add('wide');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#bordered) {
|
||||||
|
this.tooltip.classList.add('bordered-tooltip');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@inheritdoc */
|
||||||
|
lockTooltip() {
|
||||||
|
const clone = super.lockTooltip();
|
||||||
|
if (this.#wide) clone.classList.add('wide');
|
||||||
|
if (this.#bordered) clone.classList.add('bordered-tooltip');
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get HTML for Battlepoints tooltip */
|
||||||
|
async getBattlepointHTML(combatId) {
|
||||||
|
const combat = game.combats.get(combatId);
|
||||||
|
const adversaries =
|
||||||
|
combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
|
||||||
|
const characters = combat.turns?.filter(x => !x.isNPC && x.actor) ?? [];
|
||||||
|
|
||||||
|
const nrCharacters = characters.length;
|
||||||
|
const currentBP = AdversaryBPPerEncounter(adversaries, characters);
|
||||||
|
const maxBP = combat.system.extendedBattleToggles.reduce(
|
||||||
|
(acc, toggle) => acc + toggle.category,
|
||||||
|
BaseBPPerEncounter(nrCharacters)
|
||||||
|
);
|
||||||
|
|
||||||
|
const categories = combat.combatants.reduce((acc, combatant) => {
|
||||||
|
if (combatant.actor?.type === 'adversary') {
|
||||||
|
const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => {
|
||||||
|
if (identifiers) return identifiers;
|
||||||
|
const category = acc[categoryKey];
|
||||||
|
const groupingIndex = category.findIndex(grouping =>
|
||||||
|
grouping.types.includes(combatant.actor.system.type)
|
||||||
|
);
|
||||||
|
if (groupingIndex !== -1) identifiers = { categoryKey, groupingIndex };
|
||||||
|
|
||||||
|
return identifiers;
|
||||||
|
}, null);
|
||||||
|
if (keyData) {
|
||||||
|
const { categoryKey, groupingIndex } = keyData;
|
||||||
|
const grouping = acc[categoryKey][groupingIndex];
|
||||||
|
const partyAmount = CONFIG.DH.ACTOR.adversaryTypes[combatant.actor.system.type].partyAmountPerBP;
|
||||||
|
grouping.individuals = (grouping.individuals ?? 0) + 1;
|
||||||
|
|
||||||
|
const currentNr = grouping.nr ?? 0;
|
||||||
|
grouping.nr = partyAmount ? Math.ceil(grouping.individuals / (nrCharacters ?? 0)) : currentNr + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, foundry.utils.deepClone(CONFIG.DH.ENCOUNTER.adversaryTypeCostBrackets));
|
||||||
|
|
||||||
|
const extendedBattleToggles = combat.system.extendedBattleToggles;
|
||||||
|
const toggles = Object.keys(CONFIG.DH.ENCOUNTER.BPModifiers)
|
||||||
|
.reduce((acc, categoryKey) => {
|
||||||
|
const category = CONFIG.DH.ENCOUNTER.BPModifiers[categoryKey];
|
||||||
|
acc.push(
|
||||||
|
...Object.keys(category).reduce((acc, toggleKey) => {
|
||||||
|
const grouping = category[toggleKey];
|
||||||
|
acc.push({
|
||||||
|
...grouping,
|
||||||
|
categoryKey: Number(categoryKey),
|
||||||
|
toggleKey,
|
||||||
|
checked: extendedBattleToggles.find(
|
||||||
|
x => x.category == categoryKey && x.grouping === toggleKey
|
||||||
|
),
|
||||||
|
disabled: grouping.automatic
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
}, [])
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.categoryKey < b.categoryKey) return -1;
|
||||||
|
if (a.categoryKey > b.categoryKey) return 1;
|
||||||
|
else return a.toggleKey.localeCompare(b.toggleKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
return await foundry.applications.handlebars.renderTemplate(
|
||||||
|
`systems/daggerheart/templates/ui/tooltip/battlepoints.hbs`,
|
||||||
|
{
|
||||||
|
combatId: combat.id,
|
||||||
|
nrCharacters,
|
||||||
|
currentBP,
|
||||||
|
maxBP,
|
||||||
|
categories,
|
||||||
|
toggles
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Enable/disable a BP modifier */
|
||||||
|
async toggleModifier(event) {
|
||||||
|
const { combatId, category, grouping } = event.target.dataset;
|
||||||
|
const combat = game.combats.get(combatId);
|
||||||
|
await combat.update({
|
||||||
|
system: {
|
||||||
|
battleToggles: combat.system.battleToggles.some(x => x.category == category && x.grouping === grouping)
|
||||||
|
? combat.system.battleToggles.filter(x => x.category != category && x.grouping !== grouping)
|
||||||
|
: [...combat.system.battleToggles, { category: Number(category), grouping }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await combat.toggleModifierEffects(
|
||||||
|
event.target.checked,
|
||||||
|
combat.combatants.filter(x => x.actor?.type === 'adversary').map(x => x.actor),
|
||||||
|
category,
|
||||||
|
grouping
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tooltip.innerHTML = await this.getBattlepointHTML(combatId);
|
||||||
|
const lockedTooltip = this.lockTooltip();
|
||||||
|
lockedTooltip.querySelectorAll('.battlepoint-toggle-container input').forEach(element => {
|
||||||
|
element.addEventListener('input', this.toggleModifier.bind(this));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@ import { parseInlineParams } from './parser.mjs';
|
||||||
export default function DhLookupEnricher(match, { rollData }) {
|
export default function DhLookupEnricher(match, { rollData }) {
|
||||||
const results = parseInlineParams(match[1], { first: 'formula' });
|
const results = parseInlineParams(match[1], { first: 'formula' });
|
||||||
const element = document.createElement('span');
|
const element = document.createElement('span');
|
||||||
element.textContent = Roll.replaceFormulaData(String(results.formula), rollData);
|
|
||||||
|
const lookupCommand = match[0];
|
||||||
|
const lookupParam = match[1];
|
||||||
|
const lookupText = Roll.replaceFormulaData(String(results.formula), rollData);
|
||||||
|
element.textContent = lookupText === lookupParam ? lookupCommand : lookupText;
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,8 @@ export default class RegisterHandlebarsHelpers {
|
||||||
return accum;
|
return accum;
|
||||||
}
|
}
|
||||||
|
|
||||||
static damageFormula(attack, actor) {
|
static damageFormula(attack) {
|
||||||
const traitTotal = actor.system.traits?.[attack.roll.trait]?.value;
|
return attack.getDamageFormula();
|
||||||
const instances = [
|
|
||||||
attack.damage.parts.map(x => Roll.replaceFormulaData(x.value.getFormula(), actor)).join(' + '),
|
|
||||||
traitTotal
|
|
||||||
].filter(x => x);
|
|
||||||
|
|
||||||
return instances.join(traitTotal > 0 ? ' + ' : ' - ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static formulaValue(formula, item) {
|
static formulaValue(formula, item) {
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
|
||||||
spellcheck='false'
|
spellcheck='false'
|
||||||
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||||
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||||
data-tooltip="${tagData.description || tagData.name}"
|
data-tooltip="${tagData.description ? htmlToText(tagData.description) : tagData.name}"
|
||||||
${this.getAttributes(tagData)}>
|
${this.getAttributes(tagData)}>
|
||||||
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -198,7 +198,7 @@ foundry.dice.terms.Die.prototype.selfCorrecting = function (modifier) {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDamageKey = damage => {
|
export const getDamageKey = damage => {
|
||||||
return ['none', 'minor', 'major', 'severe', 'any'][damage];
|
return ['none', 'minor', 'major', 'severe', 'massive', 'any'][damage];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDamageLabel = damage => {
|
export const getDamageLabel = damage => {
|
||||||
|
|
@ -211,7 +211,8 @@ export const damageKeyToNumber = key => {
|
||||||
minor: 1,
|
minor: 1,
|
||||||
major: 2,
|
major: 2,
|
||||||
severe: 3,
|
severe: 3,
|
||||||
any: 4
|
massive: 4,
|
||||||
|
any: 5
|
||||||
}[key];
|
}[key];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -451,3 +452,32 @@ export async function waitForDiceSoNice(message) {
|
||||||
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function refreshIsAllowed(allowedTypes, typeToCheck) {
|
||||||
|
switch (typeToCheck) {
|
||||||
|
case CONFIG.DH.GENERAL.refreshTypes.scene.id:
|
||||||
|
case CONFIG.DH.GENERAL.refreshTypes.session.id:
|
||||||
|
case CONFIG.DH.GENERAL.refreshTypes.longRest.id:
|
||||||
|
return allowedTypes.includes(typeToCheck);
|
||||||
|
case CONFIG.DH.GENERAL.refreshTypes.shortRest.id:
|
||||||
|
return allowedTypes.some(
|
||||||
|
x =>
|
||||||
|
x === CONFIG.DH.GENERAL.refreshTypes.shortRest.id ||
|
||||||
|
x === CONFIG.DH.GENERAL.refreshTypes.longRest.id
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCritDamageBonus(formula) {
|
||||||
|
const critRoll = new Roll(formula);
|
||||||
|
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function htmlToText(html) {
|
||||||
|
var tempDivElement = document.createElement('div');
|
||||||
|
tempDivElement.innerHTML = html;
|
||||||
|
|
||||||
|
return tempDivElement.textContent || tempDivElement.innerText || '';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,16 @@ export const registerDHSettings = () => {
|
||||||
registerMenuSettings();
|
registerMenuSettings();
|
||||||
registerMenus();
|
registerMenus();
|
||||||
registerNonConfigSettings();
|
registerNonConfigSettings();
|
||||||
|
|
||||||
|
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue, {
|
||||||
|
name: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.SpotlightRequestQueue.name'),
|
||||||
|
label: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.SpotlightRequestQueue.label'),
|
||||||
|
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Menu.SpotlightRequestQueue.hint'),
|
||||||
|
scope: 'world',
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
onChange: () => ui.combat.render()
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerMenuSettings = () => {
|
const registerMenuSettings = () => {
|
||||||
|
|
@ -36,6 +46,9 @@ const registerMenuSettings = () => {
|
||||||
if (value.maxFear) {
|
if (value.maxFear) {
|
||||||
if (ui.resources) ui.resources.render({ force: true });
|
if (ui.resources) ui.resources.render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some homebrew settings may change sheets in various ways, so trigger a re-render
|
||||||
|
resetActors();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -130,3 +143,25 @@ const registerNonConfigSettings = () => {
|
||||||
type: DhTagTeamRoll
|
type: DhTagTeamRoll
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a reset and non-forced re-render on all given actors (if given)
|
||||||
|
* or all world actors and actors in all scenes to show immediate results for a changed setting.
|
||||||
|
*/
|
||||||
|
function resetActors(actors) {
|
||||||
|
actors ??= [
|
||||||
|
game.actors.contents,
|
||||||
|
game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])
|
||||||
|
].flat();
|
||||||
|
actors = new Set(actors);
|
||||||
|
for (const actor of actors) {
|
||||||
|
for (const app of Object.values(actor.apps)) {
|
||||||
|
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
|
||||||
|
element.open = false; // This triggers a save
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.reset();
|
||||||
|
actor.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ export const GMUpdateEvent = {
|
||||||
|
|
||||||
export const RefreshType = {
|
export const RefreshType = {
|
||||||
Countdown: 'DhCoundownRefresh',
|
Countdown: 'DhCoundownRefresh',
|
||||||
TagTeamRoll: 'DhTagTeamRollRefresh'
|
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||||
|
EffectsDisplay: 'DhEffectsDisplayRefresh'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const registerSocketHooks = () => {
|
export const registerSocketHooks = () => {
|
||||||
|
|
@ -72,10 +73,13 @@ export const registerSocketHooks = () => {
|
||||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
break;
|
break;
|
||||||
case GMUpdateEvent.UpdateSaveMessage:
|
case GMUpdateEvent.UpdateSaveMessage:
|
||||||
const action = await fromUuid(data.update.action),
|
const message = game.messages.get(data.update.message);
|
||||||
message = game.messages.get(data.update.message);
|
if (!message) return;
|
||||||
if (!action || !message) return;
|
game.system.api.fields.ActionFields.SaveField.updateSaveMessage(
|
||||||
action.updateSaveMessage(data.update.result, message, data.update.token);
|
data.update.result,
|
||||||
|
message,
|
||||||
|
data.update.token
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"readline": "^1.3.0",
|
"readline": "^1.3.0",
|
||||||
"pushLDBtoYML": "node ./tools/pushLDBtoYML.mjs",
|
"pushLDBtoYML": "node ./tools/pushLDBtoYML.mjs",
|
||||||
"pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs",
|
"pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs",
|
||||||
"pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDBBuild.mjs",
|
"pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build",
|
||||||
"createSymlink": "node ./tools/create-symlink.mjs",
|
"createSymlink": "node ./tools/create-symlink.mjs",
|
||||||
"setup:dev": "node ./tools/dev-setup.mjs"
|
"setup:dev": "node ./tools/dev-setup.mjs"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,8 @@
|
||||||
"experiences": {
|
"experiences": {
|
||||||
"pe7OIoJsqlpMXEvs": {
|
"pe7OIoJsqlpMXEvs": {
|
||||||
"name": "Tremor Sense",
|
"name": "Tremor Sense",
|
||||||
"value": 2
|
"value": 2,
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bonuses": {
|
"bonuses": {
|
||||||
|
|
@ -148,20 +149,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 75,
|
"page": 75,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "large"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1754010222829,
|
|
||||||
"modifiedTime": 1755384241210,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"ei8OkswTzyDp4IGC": 3,
|
"ei8OkswTzyDp4IGC": 3,
|
||||||
|
|
@ -269,14 +260,14 @@
|
||||||
"_id": "MFmGN6Tbf5GYxrQ9",
|
"_id": "MFmGN6Tbf5GYxrQ9",
|
||||||
"img": "icons/magic/unholy/silhouette-evil-horned-giant.webp",
|
"img": "icons/magic/unholy/silhouette-evil-horned-giant.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Burrower can be spotlighted up to three times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
"description": "<p>The @Lookup[@name] can be spotlighted up to three times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"3lGGgkxnzgUwHGIp": {
|
"3lGGgkxnzgUwHGIp": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "3lGGgkxnzgUwHGIp",
|
"_id": "3lGGgkxnzgUwHGIp",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Burrower can be spotlighted up to three times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
"description": "",
|
||||||
"chatDisplay": false,
|
"chatDisplay": false,
|
||||||
"actionType": "passive",
|
"actionType": "passive",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -318,14 +309,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY",
|
|
||||||
"modifiedTime": 1760033015502
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!89yAh30vaNQOALlz.MFmGN6Tbf5GYxrQ9"
|
"_key": "!actors.items!89yAh30vaNQOALlz.MFmGN6Tbf5GYxrQ9"
|
||||||
},
|
},
|
||||||
|
|
@ -335,14 +319,14 @@
|
||||||
"_id": "ctXYwil2D1zfsekT",
|
"_id": "ctXYwil2D1zfsekT",
|
||||||
"img": "icons/magic/earth/barrier-stone-explosion-red.webp",
|
"img": "icons/magic/earth/barrier-stone-explosion-red.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Mark a Stress</strong> to have the Burrower burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "<p><strong>Mark a Stress</strong> to have the @Lookup[@name] burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"4ppSeiTdbqnMzWAs": {
|
"4ppSeiTdbqnMzWAs": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "4ppSeiTdbqnMzWAs",
|
"_id": "4ppSeiTdbqnMzWAs",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Mark a Stress</strong> to have the Burrower burst out of the ground. All creatures within Very Close range must succeed on an Agility Reaction Roll or be knocked over, making them Vulnerable until they next act.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -399,7 +383,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
|
|
@ -436,13 +421,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"lastModifiedBy": null
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!89yAh30vaNQOALlz.ctXYwil2D1zfsekT.9PsnogEPsp1OOK64"
|
"_key": "!actors.items.effects!89yAh30vaNQOALlz.ctXYwil2D1zfsekT.9PsnogEPsp1OOK64"
|
||||||
}
|
}
|
||||||
|
|
@ -455,14 +434,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8",
|
|
||||||
"modifiedTime": 1754143640417
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!89yAh30vaNQOALlz.ctXYwil2D1zfsekT"
|
"_key": "!actors.items!89yAh30vaNQOALlz.ctXYwil2D1zfsekT"
|
||||||
},
|
},
|
||||||
|
|
@ -472,14 +444,14 @@
|
||||||
"_id": "UpFsnlbZkyvM2Ftv",
|
"_id": "UpFsnlbZkyvM2Ftv",
|
||||||
"img": "icons/magic/acid/projectile-smoke-glowing.webp",
|
"img": "icons/magic/acid/projectile-smoke-glowing.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
|
"description": "<p>Make an attack against all targets in front of the @Lookup[@name] within Close range. Targets the @Lookup[@name] succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"yd10HwK6Wa3OEvv2": {
|
"yd10HwK6Wa3OEvv2": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "yd10HwK6Wa3OEvv2",
|
"_id": "yd10HwK6Wa3OEvv2",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Make an attack against all targets in front of the Burrower within Close range. Targets the Burrower succeeds against take <strong>2d6</strong> physical damage and must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP and you gain a Fear.</p><p>@Template[type:inFront|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -577,7 +549,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
|
|
@ -588,14 +561,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY",
|
|
||||||
"modifiedTime": 1760019840573
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!89yAh30vaNQOALlz.UpFsnlbZkyvM2Ftv"
|
"_key": "!actors.items!89yAh30vaNQOALlz.UpFsnlbZkyvM2Ftv"
|
||||||
},
|
},
|
||||||
|
|
@ -605,14 +571,14 @@
|
||||||
"_id": "aNIVT5LKhwLyjKpI",
|
"_id": "aNIVT5LKhwLyjKpI",
|
||||||
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
|
"img": "icons/magic/acid/dissolve-drip-droplet-smoke.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the Burrower takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the Burrower who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>When the @Lookup[@name] takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the @Lookup[@name] who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"XbtTzOBvlTaxOKTy": {
|
"XbtTzOBvlTaxOKTy": {
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
"_id": "XbtTzOBvlTaxOKTy",
|
"_id": "XbtTzOBvlTaxOKTy",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When the Burrower takes Severe damage, all creatures within Close range are bathed in their acidic blood, taking <strong>1d10</strong> physical damage. This splash covers the ground within Very Close range with blood, and all creatures other than the Burrower who move through it take <strong>1d6</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -666,7 +632,7 @@
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
"_id": "xpcp1ECTWF20kxve",
|
"_id": "xpcp1ECTWF20kxve",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>This splash covers the ground within Very Close range with blood, and all creatures other than the Burrower who move through it take <strong>1d6</strong> physical damage.</p>",
|
"description": "<p>This splash covers the ground within Very Close range with blood, and all creatures other than the @Lookup[@name] who move through it take <strong>1d6</strong> physical damage.</p>",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -719,7 +685,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
|
|
@ -730,14 +697,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8",
|
|
||||||
"modifiedTime": 1754143695127
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!89yAh30vaNQOALlz.aNIVT5LKhwLyjKpI"
|
"_key": "!actors.items!89yAh30vaNQOALlz.aNIVT5LKhwLyjKpI"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,20 +111,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 91,
|
"page": 91,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "gargantuan"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784219,
|
|
||||||
"modifiedTime": 1755385356620,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "G7jiltRjgvVhZewm",
|
"_id": "G7jiltRjgvVhZewm",
|
||||||
"sort": 3400000,
|
"sort": 3400000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -231,14 +221,14 @@
|
||||||
"name": "Relentless (4)",
|
"name": "Relentless (4)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Flickerfly can be spotlighted up to four times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
"description": "<p>The @Lookup[@name] can be spotlighted up to four times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"poUhJdSkhjiVL2Vp": {
|
"poUhJdSkhjiVL2Vp": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "poUhJdSkhjiVL2Vp",
|
"_id": "poUhJdSkhjiVL2Vp",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Flickerfly can be spotlighted up to four times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "passive",
|
"actionType": "passive",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -274,22 +264,14 @@
|
||||||
"img": "icons/magic/unholy/silhouette-evil-horned-giant.webp",
|
"img": "icons/magic/unholy/silhouette-evil-horned-giant.webp",
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 100000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"MQSznptE5yLT7kj8": 3
|
"MQSznptE5yLT7kj8": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1754121940551,
|
|
||||||
"modifiedTime": 1760381115291,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.fFOhhMl4SDUnYNNO"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.fFOhhMl4SDUnYNNO"
|
||||||
},
|
},
|
||||||
|
|
@ -297,14 +279,14 @@
|
||||||
"name": "Never Misses",
|
"name": "Never Misses",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the Flickerfly makes an attack, the target’s Evasion is halved against the attack.</p>",
|
"description": "<p>When the @Lookup[@name] makes an attack, the target’s Evasion is halved against the attack.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"VRGPnDhDpReXUZZF": {
|
"VRGPnDhDpReXUZZF": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "VRGPnDhDpReXUZZF",
|
"_id": "VRGPnDhDpReXUZZF",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When the Flickerfly makes an attack, the target’s Evasion is halved against the attack.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "passive",
|
"actionType": "passive",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -373,36 +355,20 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754122100514,
|
|
||||||
"modifiedTime": 1754122126474,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!G7jiltRjgvVhZewm.PrwC6RpsP12fPUwy.ia0NUIOxE3RHb45P"
|
"_key": "!actors.items.effects!G7jiltRjgvVhZewm.PrwC6RpsP12fPUwy.ia0NUIOxE3RHb45P"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 200000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"MQSznptE5yLT7kj8": 3
|
"MQSznptE5yLT7kj8": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1754121968284,
|
|
||||||
"modifiedTime": 1760381147421,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.PrwC6RpsP12fPUwy"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.PrwC6RpsP12fPUwy"
|
||||||
},
|
},
|
||||||
|
|
@ -410,14 +376,14 @@
|
||||||
"name": "Whirlwind",
|
"name": "Whirlwind",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to whirl, making an attack against all targets within Very Close range. Targets the Flickerfly succeeds against take <strong>3d8</strong> direct physical damage.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "<p><strong>Spend a Fear</strong> to whirl, making an attack against all targets within Very Close range. Targets the @Lookup[@name] succeeds against take <strong>3d8</strong> direct physical damage.</p><p>@Template[type:emanation|range:vc]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"RV1wKufKrMPN6MOo": {
|
"RV1wKufKrMPN6MOo": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "RV1wKufKrMPN6MOo",
|
"_id": "RV1wKufKrMPN6MOo",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to whirl, making an attack against all targets within Very Close range. Targets the Flickerfly succeeds against take <strong>3d8</strong> direct physical damage.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -462,7 +428,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"includeBase": false
|
"includeBase": false,
|
||||||
|
"direct": true
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
"type": "any",
|
"type": "any",
|
||||||
|
|
@ -495,28 +462,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "BuL6ndgaiJtjaM2T",
|
"_id": "BuL6ndgaiJtjaM2T",
|
||||||
"img": "icons/skills/melee/strike-slashes-orange.webp",
|
"img": "icons/skills/melee/strike-slashes-orange.webp",
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 300000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"MQSznptE5yLT7kj8": 3
|
"MQSznptE5yLT7kj8": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754122005921,
|
|
||||||
"modifiedTime": 1754122771556,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.BuL6ndgaiJtjaM2T"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.BuL6ndgaiJtjaM2T"
|
||||||
},
|
},
|
||||||
|
|
@ -524,14 +484,14 @@
|
||||||
"name": "Mind Dance",
|
"name": "Mind Dance",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Mark a Stress</strong> to create a magically dazzling display that grapples the minds of nearby foes. All targets within Close range must make an Instinct Reaction Roll. For each target who failed, you gain a Fear and the Flickerfl y learns one of the target’s fears.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p><strong>Mark a Stress</strong> to create a magically dazzling display that grapples the minds of nearby foes. All targets within Close range must make an Instinct Reaction Roll. For each target who failed, you gain a Fear and the @Lookup[@name] learns one of the target’s fears.</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"GNwsDlCabx3fiG4g": {
|
"GNwsDlCabx3fiG4g": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "GNwsDlCabx3fiG4g",
|
"_id": "GNwsDlCabx3fiG4g",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Mark a Stress</strong> to create a magically dazzling display that grapples the minds of nearby foes. All targets within Close range must make an Instinct Reaction Roll. For each target who failed, you gain a Fear and the Flickerfl y learns one of the target’s fears.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -582,28 +542,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "bOTsfXr9yNIGkIzK",
|
"_id": "bOTsfXr9yNIGkIzK",
|
||||||
"img": "icons/magic/light/explosion-glow-spiral-yellow.webp",
|
"img": "icons/magic/light/explosion-glow-spiral-yellow.webp",
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 400000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"MQSznptE5yLT7kj8": 3
|
"MQSznptE5yLT7kj8": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754122013435,
|
|
||||||
"modifiedTime": 1754122660497,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.bOTsfXr9yNIGkIzK"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.bOTsfXr9yNIGkIzK"
|
||||||
},
|
},
|
||||||
|
|
@ -611,14 +564,14 @@
|
||||||
"name": "Hallucinatory Breath",
|
"name": "Hallucinatory Breath",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><em>Countdown (Loop 1d6)</em>. When the Flickerfly takes damage for the first time, activate the countdown. When it triggers, the Flickerfly breathes hallucinatory gas on all targets in front of them up to Far range. Targets must make an Instinct Reaction Roll or be tormented by fearful hallucinations. Targets whose fears are known to the Flickerfl y have disadvantage on this roll. Targets who fail lose 2 Hope and take <strong>3d8+3</strong> direct magic damage.</p><p>@Template[type:inFront|range:f]</p>",
|
"description": "<p><em>Countdown (Loop 1d6)</em>. When the @Lookup[@name] takes damage for the first time, activate the countdown. When it triggers, the @Lookup[@name] breathes hallucinatory gas on all targets in front of them up to Far range. Targets must make an Instinct Reaction Roll or be tormented by fearful hallucinations. Targets whose fears are known to the @Lookup[@name] have disadvantage on this roll. Targets who fail lose 2 Hope and take <strong>3d8+3</strong> direct magic damage.</p><p>@Template[type:inFront|range:f]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"YOyKyKGTUEWkMmJe": {
|
"YOyKyKGTUEWkMmJe": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "YOyKyKGTUEWkMmJe",
|
"_id": "YOyKyKGTUEWkMmJe",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Flickerfly breathes hallucinatory gas on all targets in front of them up to Far range. Targets must make an Instinct Reaction Roll or be tormented by fearful hallucinations. Targets whose fears are known to the Flickerfly have disadvantage on this roll. Targets who fail lose 2 Hope and take <strong>3d8+3</strong> direct magic damage.</p><p>@Template[type:inFront|range:f]</p>",
|
"description": "<p>The @Lookup[@name] breathes hallucinatory gas on all targets in front of them up to Far range. Targets must make an Instinct Reaction Roll or be tormented by fearful hallucinations. Targets whose fears are known to the @Lookup[@name] have disadvantage on this roll. Targets who fail lose 2 Hope and take <strong>3d8+3</strong> direct magic damage.</p><p>@Template[type:inFront|range:f]</p>",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -716,7 +669,7 @@
|
||||||
"type": "countdown",
|
"type": "countdown",
|
||||||
"_id": "lBhmLc33pcXzJHT3",
|
"_id": "lBhmLc33pcXzJHT3",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><em>Countdown (Loop 1d6)</em>. When the Flickerfly takes damage for the first time, activate the countdown.</p>",
|
"description": "<p><em>Countdown (Loop 1d6)</em>. When the @Lookup[@name] takes damage for the first time, activate the countdown.</p>",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"originItem": {
|
"originItem": {
|
||||||
"type": "itemCollection"
|
"type": "itemCollection"
|
||||||
|
|
@ -751,28 +704,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"_id": "49cIxZRFiAM6jDva",
|
"_id": "49cIxZRFiAM6jDva",
|
||||||
"img": "icons/magic/air/fog-gas-smoke-purple.webp",
|
"img": "icons/magic/air/fog-gas-smoke-purple.webp",
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 500000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"MQSznptE5yLT7kj8": 3
|
"MQSznptE5yLT7kj8": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.351",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.2.4",
|
|
||||||
"createdTime": 1754122030308,
|
|
||||||
"modifiedTime": 1763599149242,
|
|
||||||
"lastModifiedBy": "Q4RzhhaPfvLUzzbw"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.49cIxZRFiAM6jDva"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.49cIxZRFiAM6jDva"
|
||||||
},
|
},
|
||||||
|
|
@ -780,14 +726,14 @@
|
||||||
"name": "Uncanny Reflexes",
|
"name": "Uncanny Reflexes",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the Flickerfly takes damage from an attack within Close range, you can mark a Stress to take half damage.</p>",
|
"description": "<p>When the @Lookup[@name] takes damage from an attack within Close range, you can mark a Stress to take half damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"FocbilGTpvUjlb7m": {
|
"FocbilGTpvUjlb7m": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "FocbilGTpvUjlb7m",
|
"_id": "FocbilGTpvUjlb7m",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When the Flickerfly takes damage from an attack within Close range, you can mark a Stress to take half damage.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "reaction",
|
"actionType": "reaction",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -814,28 +760,21 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"_id": "KLdLRKoJHBJlHwYe",
|
"_id": "KLdLRKoJHBJlHwYe",
|
||||||
"img": "icons/skills/movement/arrow-upward-yellow.webp",
|
"img": "icons/skills/movement/arrow-upward-yellow.webp",
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 600000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"MQSznptE5yLT7kj8": 3
|
"MQSznptE5yLT7kj8": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1754122789186,
|
|
||||||
"modifiedTime": 1760381159787,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.KLdLRKoJHBJlHwYe"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.KLdLRKoJHBJlHwYe"
|
||||||
},
|
},
|
||||||
|
|
@ -843,7 +782,7 @@
|
||||||
"name": "Deadly Flight",
|
"name": "Deadly Flight",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>While flying the Flickerfly can move up to Far range instead of Close range before taking an action.</p>",
|
"description": "<p>While flying the @Lookup[@name] can move up to Far range instead of Close range before taking an action.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -853,22 +792,14 @@
|
||||||
"img": "icons/skills/movement/feet-winged-boots-blue.webp",
|
"img": "icons/skills/movement/feet-winged-boots-blue.webp",
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
"sort": 0,
|
"sort": 700000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
"default": 0,
|
"default": 0,
|
||||||
"fBcTgyTzoARBvohY": 3
|
"fBcTgyTzoARBvohY": 3
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1760381172325,
|
|
||||||
"modifiedTime": 1760381302938,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!G7jiltRjgvVhZewm.WFRpSDZvqF0Upjoy"
|
"_key": "!actors.items!G7jiltRjgvVhZewm.WFRpSDZvqF0Upjoy"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,20 +110,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 84,
|
"page": 84,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "medium"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784220,
|
|
||||||
"modifiedTime": 1755384980487,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "vNIbYQ4YSzNf0WPE",
|
"_id": "vNIbYQ4YSzNf0WPE",
|
||||||
"sort": 3500000,
|
"sort": 3500000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -230,11 +220,12 @@
|
||||||
"name": "Minion (6)",
|
"name": "Minion (6)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Assassin is defeated when they take any damage. For every 6 damage a PC deals to the Assassin, defeat an additional Minion within range the attack would succeed against.</p>",
|
"description": "<p>The @Lookup[@name] is defeated when they take any damage. For every 6 damage a PC deals to the @Lookup[@name], defeat an additional Minion within range the attack would succeed against.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "passive"
|
||||||
},
|
},
|
||||||
"_id": "2yREz60uPY80tAa4",
|
"_id": "2yREz60uPY80tAa4",
|
||||||
"img": "icons/magic/symbols/runes-carved-stone-yellow.webp",
|
"img": "icons/magic/symbols/runes-carved-stone-yellow.webp",
|
||||||
|
|
@ -247,15 +238,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754070516177,
|
|
||||||
"modifiedTime": 1754070536166,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!vNIbYQ4YSzNf0WPE.2yREz60uPY80tAa4"
|
"_key": "!actors.items!vNIbYQ4YSzNf0WPE.2yREz60uPY80tAa4"
|
||||||
},
|
},
|
||||||
|
|
@ -263,14 +246,14 @@
|
||||||
"name": "Group Attack",
|
"name": "Group Attack",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all Apprentice Assassins within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 4 physical damage each. Combine this damage.</p>",
|
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all @Lookup[@name]s within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 4 physical damage each. Combine this damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"vgguNWz8vG8aoLXR": {
|
"vgguNWz8vG8aoLXR": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "vgguNWz8vG8aoLXR",
|
"_id": "vgguNWz8vG8aoLXR",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to choose a target and spotlight all Apprentice Assassins within Close range of them. Those Minions move into Melee range of the target and make one shared attack roll. On a success, they deal 4 physical damage each. Combine this damage.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -297,7 +280,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "4wT7CmM1DJEPcraF",
|
"_id": "4wT7CmM1DJEPcraF",
|
||||||
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
"img": "icons/creatures/abilities/tail-strike-bone-orange.webp",
|
||||||
|
|
@ -310,15 +294,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754070539112,
|
|
||||||
"modifiedTime": 1754142025997,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!vNIbYQ4YSzNf0WPE.4wT7CmM1DJEPcraF"
|
"_key": "!actors.items!vNIbYQ4YSzNf0WPE.4wT7CmM1DJEPcraF"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,20 +122,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 97,
|
"page": 97,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "medium"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784221,
|
|
||||||
"modifiedTime": 1755385620034,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "WPEOIGfclNJxWb87",
|
"_id": "WPEOIGfclNJxWb87",
|
||||||
"sort": 1200000,
|
"sort": 1200000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -249,7 +239,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "wi2DDvBhlg6sxQoc",
|
"_id": "wi2DDvBhlg6sxQoc",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Mark a Stress</strong> to spotlight <strong>1d4</strong> allies. Attacks they make while spotlighted in this way deal half damage, or full damage if you spend a Fear.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -300,7 +290,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "jNmMyq5QI2HNgffy",
|
"_id": "jNmMyq5QI2HNgffy",
|
||||||
"img": "icons/magic/death/skull-weapon-staff-glow-pink.webp",
|
"img": "icons/magic/death/skull-weapon-staff-glow-pink.webp",
|
||||||
|
|
@ -313,15 +304,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754132259208,
|
|
||||||
"modifiedTime": 1754132328745,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!WPEOIGfclNJxWb87.jNmMyq5QI2HNgffy"
|
"_key": "!actors.items!WPEOIGfclNJxWb87.jNmMyq5QI2HNgffy"
|
||||||
},
|
},
|
||||||
|
|
@ -336,7 +319,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "vaXLESD4sRkQ3Ahn",
|
"_id": "vaXLESD4sRkQ3Ahn",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Mark 2 Stress</strong> to cause all targets within Far range to make a Strength Reaction Roll. Targets who fail take <strong>2d20+12</strong> magic damage and you gain a Fear. Targets who succeed take half damage. A target who marks 2 or more HP must also <strong>mark 2 Stress</strong> and becomes <em>Vulnerable</em> until they roll with Hope.</p><p>@Template[type:emanation|range:f]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -486,7 +469,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "4EECsXzHFG0RoIg0",
|
"_id": "4EECsXzHFG0RoIg0",
|
||||||
"img": "icons/magic/unholy/projectile-missile-green.webp",
|
"img": "icons/magic/unholy/projectile-missile-green.webp",
|
||||||
|
|
@ -525,15 +509,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754132456667,
|
|
||||||
"modifiedTime": 1754132472474,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!WPEOIGfclNJxWb87.4EECsXzHFG0RoIg0.KGdf2eqcXkdigg0u"
|
"_key": "!actors.items.effects!WPEOIGfclNJxWb87.4EECsXzHFG0RoIg0.KGdf2eqcXkdigg0u"
|
||||||
}
|
}
|
||||||
|
|
@ -546,15 +522,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754132335603,
|
|
||||||
"modifiedTime": 1754132528280,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!WPEOIGfclNJxWb87.4EECsXzHFG0RoIg0"
|
"_key": "!actors.items!WPEOIGfclNJxWb87.4EECsXzHFG0RoIg0"
|
||||||
},
|
},
|
||||||
|
|
@ -569,7 +537,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "gZg3AkzCYUTExjE6",
|
"_id": "gZg3AkzCYUTExjE6",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to summon a @UUID[Compendium.daggerheart.adversaries.Actor.YhJrP7rTBiRdX5Fp]{Zombie Legion}, which appears at Close range and immediately takes the spotlight.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -596,7 +564,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "XxXOrFovbCz9zFxR",
|
"_id": "XxXOrFovbCz9zFxR",
|
||||||
"img": "icons/magic/death/undead-zombie-grave-green.webp",
|
"img": "icons/magic/death/undead-zombie-grave-green.webp",
|
||||||
|
|
@ -609,15 +578,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754132531238,
|
|
||||||
"modifiedTime": 1754133089602,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!WPEOIGfclNJxWb87.XxXOrFovbCz9zFxR"
|
"_key": "!actors.items!WPEOIGfclNJxWb87.XxXOrFovbCz9zFxR"
|
||||||
},
|
},
|
||||||
|
|
@ -625,14 +586,14 @@
|
||||||
"name": "Not Today, My Dears",
|
"name": "Not Today, My Dears",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the Necromancer has marked 7 or more of their HP, you can <strong>spend a Fear</strong> to have them teleport away to a safe location to recover. A PC who succeeds on an Instinct Roll can trace the teleportation magic to their destination.</p>",
|
"description": "<p>When the @Lookup[@name] has marked 7 or more of their HP, you can <strong>spend a Fear</strong> to have them teleport away to a safe location to recover. A PC who succeeds on an Instinct Roll can trace the teleportation magic to their destination.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"DX8WPeLVrRBB2CdM": {
|
"DX8WPeLVrRBB2CdM": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "DX8WPeLVrRBB2CdM",
|
"_id": "DX8WPeLVrRBB2CdM",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When the Necromancer has marked 7 or more of their HP, you can <strong>spend a Fear</strong> to have them teleport away to a safe location to recover. A PC who succeeds on an Instinct Roll can trace the teleportation magic to their destination.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -683,7 +644,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"_id": "k4MSykLRoW3qp7Lk",
|
"_id": "k4MSykLRoW3qp7Lk",
|
||||||
"img": "icons/magic/death/skull-horned-worn-fire-blue.webp",
|
"img": "icons/magic/death/skull-horned-worn-fire-blue.webp",
|
||||||
|
|
@ -696,15 +658,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754132587562,
|
|
||||||
"modifiedTime": 1754132653034,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!WPEOIGfclNJxWb87.k4MSykLRoW3qp7Lk"
|
"_key": "!actors.items!WPEOIGfclNJxWb87.k4MSykLRoW3qp7Lk"
|
||||||
},
|
},
|
||||||
|
|
@ -712,14 +666,14 @@
|
||||||
"name": "Your Life Is Mine",
|
"name": "Your Life Is Mine",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><em>Countdown (Loop 2d6)</em>. When the Necromancer has marked 6 or more of their HP, activate the countdown. When it triggers, deal <strong>2d10+6</strong> direct magic damage to a target within Close range. The Necromancer then <strong>clears a number of Stress or HP</strong> equal to the number of HP marked by the target from this attack.</p>",
|
"description": "<p><em>Countdown (Loop 2d6)</em>. When the @Lookup[@name] has marked 6 or more of their HP, activate the countdown. When it triggers, deal <strong>2d10+6</strong> direct magic damage to a target within Close range. The @Lookup[@name] then <strong>clears a number of Stress or HP</strong> equal to the number of HP marked by the target from this attack.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"YzepYov9vEMcBPU1": {
|
"YzepYov9vEMcBPU1": {
|
||||||
"type": "damage",
|
"type": "damage",
|
||||||
"_id": "YzepYov9vEMcBPU1",
|
"_id": "YzepYov9vEMcBPU1",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Deal <strong>2d10+6</strong> direct magic damage to a target within Close range. The Necromancer then <strong>clears a number of Stress or HP</strong> equal to the number of HP marked by the target from this attack.</p>",
|
"description": "<p>Deal <strong>2d10+6</strong> direct magic damage to a target within Close range. The @Lookup[@name] then <strong>clears a number of Stress or HP</strong> equal to the number of HP marked by the target from this attack.</p>",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -772,7 +726,7 @@
|
||||||
"type": "countdown",
|
"type": "countdown",
|
||||||
"_id": "LXhwkNCDFeUric8D",
|
"_id": "LXhwkNCDFeUric8D",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><em>Countdown (Loop 2d6)</em>. When the Necromancer has marked 6 or more of their HP, activate the countdown.</p>",
|
"description": "<p><em>Countdown (Loop 2d6)</em>. When the @Lookup[@name] has marked 6 or more of their HP, activate the countdown.</p>",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"originItem": {
|
"originItem": {
|
||||||
"type": "itemCollection"
|
"type": "itemCollection"
|
||||||
|
|
@ -807,7 +761,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"_id": "FKcuCo0v2U7fVkqq",
|
"_id": "FKcuCo0v2U7fVkqq",
|
||||||
"img": "icons/magic/unholy/hand-claw-fire-green.webp",
|
"img": "icons/magic/unholy/hand-claw-fire-green.webp",
|
||||||
|
|
@ -820,15 +775,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.351",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.2.4",
|
|
||||||
"createdTime": 1754132668836,
|
|
||||||
"modifiedTime": 1763599179169,
|
|
||||||
"lastModifiedBy": "Q4RzhhaPfvLUzzbw"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!WPEOIGfclNJxWb87.FKcuCo0v2U7fVkqq"
|
"_key": "!actors.items!WPEOIGfclNJxWb87.FKcuCo0v2U7fVkqq"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@
|
||||||
"experiences": {
|
"experiences": {
|
||||||
"Gtr9I2G39GcXT2Si": {
|
"Gtr9I2G39GcXT2Si": {
|
||||||
"name": "Local Knowledge",
|
"name": "Local Knowledge",
|
||||||
"value": 3
|
"value": 3,
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bonuses": {
|
"bonuses": {
|
||||||
|
|
@ -116,20 +117,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 77,
|
"page": 77,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "medium"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784222,
|
|
||||||
"modifiedTime": 1755384306205,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "JRhrrEg5UroURiAD",
|
"_id": "JRhrrEg5UroURiAD",
|
||||||
"sort": 2900000,
|
"sort": 2900000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -245,7 +236,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "84rwldOFvTPrrHJJ",
|
"_id": "84rwldOFvTPrrHJJ",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Make an attack against a target within Far range. On a success, <strong>mark a Stress</strong> to deal <strong>1d12+3</strong> physical damage. If the target marks HP from this attack, they have disadvantage on Agility Rolls until they clear at least 1 HP.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -322,7 +313,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
|
|
@ -364,15 +356,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754012705802,
|
|
||||||
"modifiedTime": 1754012740752,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!JRhrrEg5UroURiAD.DMtd1EXQPlPaoRmV.wGuxOLokMqdxVSOo"
|
"_key": "!actors.items.effects!JRhrrEg5UroURiAD.DMtd1EXQPlPaoRmV.wGuxOLokMqdxVSOo"
|
||||||
}
|
}
|
||||||
|
|
@ -385,15 +369,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754012548341,
|
|
||||||
"modifiedTime": 1754143709108,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!JRhrrEg5UroURiAD.DMtd1EXQPlPaoRmV"
|
"_key": "!actors.items!JRhrrEg5UroURiAD.DMtd1EXQPlPaoRmV"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,20 +111,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 84,
|
"page": 84,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "medium"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784223,
|
|
||||||
"modifiedTime": 1755384973132,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "0ts6CGd93lLqGZI5",
|
"_id": "0ts6CGd93lLqGZI5",
|
||||||
"sort": 200000,
|
"sort": 200000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -231,7 +221,7 @@
|
||||||
"name": "Horde (1d6+3)",
|
"name": "Horde (1d6+3)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the Squadron has marked half or more of their HP, their standard attack deals <strong>1d6+3</strong> physical damage instead.</p>",
|
"description": "<p>When the @Lookup[@name] has marked half or more of their HP, their standard attack deals <strong>1d6+3</strong> physical damage instead.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -248,15 +238,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754070637872,
|
|
||||||
"modifiedTime": 1754070657545,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!0ts6CGd93lLqGZI5.uPwtE9d63PHtQitG"
|
"_key": "!actors.items!0ts6CGd93lLqGZI5.uPwtE9d63PHtQitG"
|
||||||
},
|
},
|
||||||
|
|
@ -264,14 +246,14 @@
|
||||||
"name": "Focused Volley",
|
"name": "Focused Volley",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p><strong>Spend a Fear</strong> to target a point within Far range. Make an attack with advantage against all targets within Close range of that point. Targets the Squadron succeeds against take <strong>1d10+4</strong> physical damage.</p><p>@Template[type:circle|range:c]</p>",
|
"description": "<p><strong>Spend a Fear</strong> to target a point within Far range. Make an attack with advantage against all targets within Close range of that point. Targets the @Lookup[@name] succeeds against take <strong>1d10+4</strong> physical damage.</p><p>@Template[type:circle|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"uG7Hl2DqaT69aNs1": {
|
"uG7Hl2DqaT69aNs1": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "uG7Hl2DqaT69aNs1",
|
"_id": "uG7Hl2DqaT69aNs1",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Spend a Fear</strong> to target a point within Far range. Make an attack with advantage against all targets within Close range of that point. Targets the Squadron succeeds against take <strong>1d10+4</strong> physical damage.</p><p>@Template[type:circle|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -347,7 +329,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "Wuf5y9tJ88BwzLv2",
|
"_id": "Wuf5y9tJ88BwzLv2",
|
||||||
"img": "icons/skills/ranged/arrows-flying-triple-brown.webp",
|
"img": "icons/skills/ranged/arrows-flying-triple-brown.webp",
|
||||||
|
|
@ -360,15 +343,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754070661520,
|
|
||||||
"modifiedTime": 1754142041107,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!0ts6CGd93lLqGZI5.Wuf5y9tJ88BwzLv2"
|
"_key": "!actors.items!0ts6CGd93lLqGZI5.Wuf5y9tJ88BwzLv2"
|
||||||
},
|
},
|
||||||
|
|
@ -383,7 +358,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "mH6mmJIMM1fwzePt",
|
"_id": "mH6mmJIMM1fwzePt",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Mark a Stress</strong> to target a point within Far range. Until the next roll with Fear, a creature who moves within Close range of that point must make an Agility Reaction Roll. On a failure, they take <strong>2d6+3</strong> physical damage. On a success, they take half damage.</p><p>@Template[type:circle|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -452,7 +427,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "ayGHTtyjSuIR4BrV",
|
"_id": "ayGHTtyjSuIR4BrV",
|
||||||
"img": "icons/skills/ranged/arrows-flying-salvo-blue.webp",
|
"img": "icons/skills/ranged/arrows-flying-salvo-blue.webp",
|
||||||
|
|
@ -465,15 +441,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754070760732,
|
|
||||||
"modifiedTime": 1754142058270,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!0ts6CGd93lLqGZI5.ayGHTtyjSuIR4BrV"
|
"_key": "!actors.items!0ts6CGd93lLqGZI5.ayGHTtyjSuIR4BrV"
|
||||||
}
|
}
|
||||||
|
|
@ -506,15 +474,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754070598535,
|
|
||||||
"modifiedTime": 1754070598535,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.effects!0ts6CGd93lLqGZI5.bq8hTzQoCXmA3xad"
|
"_key": "!actors.effects!0ts6CGd93lLqGZI5.bq8hTzQoCXmA3xad"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,20 +117,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 84,
|
"page": 84,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "medium"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784224,
|
|
||||||
"modifiedTime": 1755384989183,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "h5RuhzGL17dW5FBT",
|
"_id": "h5RuhzGL17dW5FBT",
|
||||||
"sort": 2700000,
|
"sort": 2700000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -237,14 +227,14 @@
|
||||||
"name": "Grindletooth Venom",
|
"name": "Grindletooth Venom",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Targets who mark HP from the Assassin’s attacks are Vulnerable until they clear a HP.</p>",
|
"description": "<p>Targets who mark HP from the @Lookup[@name]’s attacks are Vulnerable until they clear a HP.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"L83tU1TgmqoH9SSn": {
|
"L83tU1TgmqoH9SSn": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "L83tU1TgmqoH9SSn",
|
"_id": "L83tU1TgmqoH9SSn",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Targets who mark HP from the Assassin’s attacks are Vulnerable until they clear a HP.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -308,15 +298,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073115997,
|
|
||||||
"modifiedTime": 1754073138695,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!h5RuhzGL17dW5FBT.Fz2lnUEeBxsDpx0G.2iBVUGHtGW3I9VIj"
|
"_key": "!actors.items.effects!h5RuhzGL17dW5FBT.Fz2lnUEeBxsDpx0G.2iBVUGHtGW3I9VIj"
|
||||||
}
|
}
|
||||||
|
|
@ -329,15 +311,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073069218,
|
|
||||||
"modifiedTime": 1754142079163,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!h5RuhzGL17dW5FBT.Fz2lnUEeBxsDpx0G"
|
"_key": "!actors.items!h5RuhzGL17dW5FBT.Fz2lnUEeBxsDpx0G"
|
||||||
},
|
},
|
||||||
|
|
@ -345,7 +319,7 @@
|
||||||
"name": "Assassin Poisoner",
|
"name": "Assassin Poisoner",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Assassin has advantage on attacks if they are <em>Hidden</em>.</p>",
|
"description": "<p>The @Lookup[@name] has advantage on attacks if they are <em>Hidden</em>.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -393,15 +367,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073175871,
|
|
||||||
"modifiedTime": 1754073220488,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!h5RuhzGL17dW5FBT.of6g4piugKzTI4dv.qT5nh7OcQ6aGdiWS"
|
"_key": "!actors.items.effects!h5RuhzGL17dW5FBT.of6g4piugKzTI4dv.qT5nh7OcQ6aGdiWS"
|
||||||
}
|
}
|
||||||
|
|
@ -414,15 +380,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073144490,
|
|
||||||
"modifiedTime": 1754073171491,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!h5RuhzGL17dW5FBT.of6g4piugKzTI4dv"
|
"_key": "!actors.items!h5RuhzGL17dW5FBT.of6g4piugKzTI4dv"
|
||||||
},
|
},
|
||||||
|
|
@ -437,7 +395,7 @@
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "sp7RfJRQJsEUm09m",
|
"_id": "sp7RfJRQJsEUm09m",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Drop a smoke bomb that fills the air within Close range with smoke, Dizzying all targets in this area. Dizzied targets have disadvantage on their next action roll, then clear the condition.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -462,7 +420,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "lAmiK8wVxjyHwKlp",
|
"_id": "lAmiK8wVxjyHwKlp",
|
||||||
"img": "icons/magic/air/fog-gas-smoke-green.webp",
|
"img": "icons/magic/air/fog-gas-smoke-green.webp",
|
||||||
|
|
@ -499,15 +458,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073324162,
|
|
||||||
"modifiedTime": 1754073362021,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!h5RuhzGL17dW5FBT.lAmiK8wVxjyHwKlp.yP4ot8VqS56RnxnE"
|
"_key": "!actors.items.effects!h5RuhzGL17dW5FBT.lAmiK8wVxjyHwKlp.yP4ot8VqS56RnxnE"
|
||||||
}
|
}
|
||||||
|
|
@ -520,15 +471,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073225985,
|
|
||||||
"modifiedTime": 1754142105436,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!h5RuhzGL17dW5FBT.lAmiK8wVxjyHwKlp"
|
"_key": "!actors.items!h5RuhzGL17dW5FBT.lAmiK8wVxjyHwKlp"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,20 +117,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 85,
|
"page": 85,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "large"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784224,
|
|
||||||
"modifiedTime": 1755385012352,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "dgH3fW9FTYLaIDvS",
|
"_id": "dgH3fW9FTYLaIDvS",
|
||||||
"sort": 2600000,
|
"sort": 2600000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -237,14 +227,14 @@
|
||||||
"name": "Relentless (2)",
|
"name": "Relentless (2)",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box can be spotlighted up to two times times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
"description": "<p>The @Lookup[@name] can be spotlighted up to two times times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"2JfPSV3pw6pv0BXd": {
|
"2JfPSV3pw6pv0BXd": {
|
||||||
"type": "effect",
|
"type": "effect",
|
||||||
"_id": "2JfPSV3pw6pv0BXd",
|
"_id": "2JfPSV3pw6pv0BXd",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box can be spotlighted up to two times times per GM turn. Spend Fear as usual to spotlight them.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "passive",
|
"actionType": "passive",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -287,15 +277,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1754073816379,
|
|
||||||
"modifiedTime": 1760211145763,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.RSovCwuGrZ1mk5py"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.RSovCwuGrZ1mk5py"
|
||||||
},
|
},
|
||||||
|
|
@ -303,14 +285,14 @@
|
||||||
"name": "Randomized Tactics",
|
"name": "Randomized Tactics",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Mark a Stress and roll a <strong>d6</strong>. The Box uses the corresponding move:</p><ol><li><p><strong>Mana Beam</strong></p></li><li><p><strong>Fire Jets</strong></p></li><li><p><strong>Trample</strong></p></li><li><p><strong>Shocking Gas</strong></p></li><li><p><strong>Stunning Clap</strong></p></li><li><p><strong>Psionic Whine</strong></p></li></ol>",
|
"description": "<p>Mark a Stress and roll a <strong>d6</strong>. The @Lookup[@name] uses the corresponding move:</p><ol><li><p><strong>Mana Beam</strong></p></li><li><p><strong>Fire Jets</strong></p></li><li><p><strong>Trample</strong></p></li><li><p><strong>Shocking Gas</strong></p></li><li><p><strong>Stunning Clap</strong></p></li><li><p><strong>Psionic Whine</strong></p></li></ol>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"FX9jwg5ZNjAWnti3": {
|
"FX9jwg5ZNjAWnti3": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "FX9jwg5ZNjAWnti3",
|
"_id": "FX9jwg5ZNjAWnti3",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Mark a Stress and roll a <strong>d6</strong>. The Box uses the corresponding move:</p><ol><li><p><strong>Mana Beam</strong></p></li><li><p><strong>Fire Jets</strong></p></li><li><p><strong>Trample</strong></p></li><li><p><strong>Shocking Gas</strong></p></li><li><p><strong>Stunning Clap</strong></p></li><li><p><strong>Psionic Whine</strong></p></li></ol>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -361,7 +343,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "ZqfLMjVkbUwDw4p6",
|
"_id": "ZqfLMjVkbUwDw4p6",
|
||||||
"img": "icons/commodities/tech/transmission.webp",
|
"img": "icons/commodities/tech/transmission.webp",
|
||||||
|
|
@ -374,15 +357,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754073845291,
|
|
||||||
"modifiedTime": 1754142121782,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.ZqfLMjVkbUwDw4p6"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.ZqfLMjVkbUwDw4p6"
|
||||||
},
|
},
|
||||||
|
|
@ -390,14 +365,14 @@
|
||||||
"name": "Mana Beam",
|
"name": "Mana Beam",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box fires a searing beam. Make an attack against a target within Far range. On a success, deal <strong>2d10+2</strong> magic damage. </p>",
|
"description": "<p>The @Lookup[@name] fires a searing beam. Make an attack against a target within Far range. On a success, deal <strong>2d10+2</strong> magic damage.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"Co09oXMw0yBjGaws": {
|
"Co09oXMw0yBjGaws": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "Co09oXMw0yBjGaws",
|
"_id": "Co09oXMw0yBjGaws",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box fires a searing beam. Make an attack against a target within Far range. On a success, deal <strong>2d10+2</strong> magic damage.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -468,7 +443,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "lqyN4CQop53BzarW",
|
"_id": "lqyN4CQop53BzarW",
|
||||||
"img": "icons/magic/light/beam-rays-blue.webp",
|
"img": "icons/magic/light/beam-rays-blue.webp",
|
||||||
|
|
@ -481,15 +457,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074104758,
|
|
||||||
"modifiedTime": 1754142132743,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.lqyN4CQop53BzarW"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.lqyN4CQop53BzarW"
|
||||||
},
|
},
|
||||||
|
|
@ -497,14 +465,14 @@
|
||||||
"name": "Fire Jets",
|
"name": "Fire Jets",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box shoots into the air, spinning and releasing jets of flame. Make an attack against all targets within Close range. Targets the Box succeeds against take <strong>2d8</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>The @Lookup[@name] shoots into the air, spinning and releasing jets of flame. Make an attack against all targets within Close range. Targets the @Lookup[@name] succeeds against take <strong>2d8</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"hRAKaOdzQXLYBNVV": {
|
"hRAKaOdzQXLYBNVV": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "hRAKaOdzQXLYBNVV",
|
"_id": "hRAKaOdzQXLYBNVV",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box shoots into the air, spinning and releasing jets of flame. Make an attack against all targets within Close range. Targets the Box succeeds against take <strong>2d8</strong> physical damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -575,7 +543,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "IHQoqt39T772FVMs",
|
"_id": "IHQoqt39T772FVMs",
|
||||||
"img": "icons/magic/fire/explosion-embers-orange.webp",
|
"img": "icons/magic/fire/explosion-embers-orange.webp",
|
||||||
|
|
@ -588,15 +557,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074175443,
|
|
||||||
"modifiedTime": 1754142142523,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.IHQoqt39T772FVMs"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.IHQoqt39T772FVMs"
|
||||||
},
|
},
|
||||||
|
|
@ -604,14 +565,14 @@
|
||||||
"name": "Trample",
|
"name": "Trample",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box rockets around erratically. Make an attack against all PCs within Close range. Targets the Box succeeds against take <strong>1d6+5</strong> physical damage and are Vulnerable until their next roll with Hope. </p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>The @Lookup[@name] rockets around erratically. Make an attack against all PCs within Close range. Targets the @Lookup[@name] succeeds against take <strong>1d6+5</strong> physical damage and are Vulnerable until their next roll with Hope.</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"IOgPMu12Xnn33TfG": {
|
"IOgPMu12Xnn33TfG": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "IOgPMu12Xnn33TfG",
|
"_id": "IOgPMu12Xnn33TfG",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box rockets around erratically. Make an attack against all PCs within Close range. Targets the Box succeeds against take <strong>1d6+5</strong> physical damage and are Vulnerable until their next roll with Hope.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -687,7 +648,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "XtnByqUr9AuYU9Ip",
|
"_id": "XtnByqUr9AuYU9Ip",
|
||||||
"img": "icons/skills/movement/arrow-upward-yellow.webp",
|
"img": "icons/skills/movement/arrow-upward-yellow.webp",
|
||||||
|
|
@ -726,15 +688,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074326119,
|
|
||||||
"modifiedTime": 1754074354525,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!dgH3fW9FTYLaIDvS.XtnByqUr9AuYU9Ip.9NQcCXMhjyBReJRd"
|
"_key": "!actors.items.effects!dgH3fW9FTYLaIDvS.XtnByqUr9AuYU9Ip.9NQcCXMhjyBReJRd"
|
||||||
}
|
}
|
||||||
|
|
@ -747,15 +701,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074260007,
|
|
||||||
"modifiedTime": 1754142151952,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.XtnByqUr9AuYU9Ip"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.XtnByqUr9AuYU9Ip"
|
||||||
},
|
},
|
||||||
|
|
@ -763,14 +709,14 @@
|
||||||
"name": "Shocking Gas",
|
"name": "Shocking Gas",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box sprays out a silver gas sparking with lightning. All targets within Close range must succeed on a Finesse Reaction Roll or mark 3 Stress. </p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>The @Lookup[@name] sprays out a silver gas sparking with lightning. All targets within Close range must succeed on a Finesse Reaction Roll or mark 3 Stress.</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"ky4OMl558J5wCbDp": {
|
"ky4OMl558J5wCbDp": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "ky4OMl558J5wCbDp",
|
"_id": "ky4OMl558J5wCbDp",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box sprays out a silver gas sparking with lightning. All targets within Close range must succeed on a Finesse Reaction Roll or mark 3 Stress.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -840,7 +786,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "3bPURmuwQs06fThQ",
|
"_id": "3bPURmuwQs06fThQ",
|
||||||
"img": "icons/magic/lightning/bolt-strike-embers-teal.webp",
|
"img": "icons/magic/lightning/bolt-strike-embers-teal.webp",
|
||||||
|
|
@ -853,15 +800,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074364206,
|
|
||||||
"modifiedTime": 1754142161096,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.3bPURmuwQs06fThQ"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.3bPURmuwQs06fThQ"
|
||||||
},
|
},
|
||||||
|
|
@ -869,14 +808,14 @@
|
||||||
"name": "Stunning Clap",
|
"name": "Stunning Clap",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box leaps and their sides clap, creating a small sonic boom. All targets within Very Close range must succeed on a Strength Reaction Roll or become <em>Vulnerable</em> until the cube is defeated.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "<p>The @Lookup[@name] leaps and their sides clap, creating a small sonic boom. All targets within Very Close range must succeed on a Strength Reaction Roll or become <em>Vulnerable</em> until the cube is defeated.</p><p>@Template[type:emanation|range:vc]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"LQtopkrtSlCQ5MAr": {
|
"LQtopkrtSlCQ5MAr": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "LQtopkrtSlCQ5MAr",
|
"_id": "LQtopkrtSlCQ5MAr",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box leaps and their sides clap, creating a small sonic boom. All targets within Very Close range must succeed on a Strength Reaction Roll or become <em>Vulnerable</em> until the cube is defeated.</p><p>@Template[type:emanation|range:vc]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -925,7 +864,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "ijIaKjroxq3xZd9Z",
|
"_id": "ijIaKjroxq3xZd9Z",
|
||||||
"img": "icons/magic/sonic/explosion-impact-shock-wave.webp",
|
"img": "icons/magic/sonic/explosion-impact-shock-wave.webp",
|
||||||
|
|
@ -964,15 +904,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074519811,
|
|
||||||
"modifiedTime": 1754074537174,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!dgH3fW9FTYLaIDvS.ijIaKjroxq3xZd9Z.S7kJlhnV8Nexzi8l"
|
"_key": "!actors.items.effects!dgH3fW9FTYLaIDvS.ijIaKjroxq3xZd9Z.S7kJlhnV8Nexzi8l"
|
||||||
}
|
}
|
||||||
|
|
@ -985,15 +917,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074454965,
|
|
||||||
"modifiedTime": 1754142182845,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.ijIaKjroxq3xZd9Z"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.ijIaKjroxq3xZd9Z"
|
||||||
},
|
},
|
||||||
|
|
@ -1001,14 +925,14 @@
|
||||||
"name": "Psionic Whine",
|
"name": "Psionic Whine",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>The Box releases a cluster of mechanical bees whose buzz rattles mortal minds. All targets within Close range must succeed on a Presence Reaction Roll or take <strong>2d4+9</strong> direct magic damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>The @Lookup[@name] releases a cluster of mechanical bees whose buzz rattles mortal minds. All targets within Close range must succeed on a Presence Reaction Roll or take <strong>2d4+9</strong> direct magic damage.</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"3R3pGOUj4rHaUzPK": {
|
"3R3pGOUj4rHaUzPK": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "3R3pGOUj4rHaUzPK",
|
"_id": "3R3pGOUj4rHaUzPK",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>The Box releases a cluster of mechanical bees whose buzz rattles mortal minds. All targets within Close range must succeed on a Presence Reaction Roll or take <strong>2d4+9</strong> direct magic damage.</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -1079,7 +1003,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"_id": "JCue4ko61bjhedXv",
|
"_id": "JCue4ko61bjhedXv",
|
||||||
"img": "icons/creatures/invertebrates/wasp-swarm-tan.webp",
|
"img": "icons/creatures/invertebrates/wasp-swarm-tan.webp",
|
||||||
|
|
@ -1092,15 +1017,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074558281,
|
|
||||||
"modifiedTime": 1754142197090,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.JCue4ko61bjhedXv"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.JCue4ko61bjhedXv"
|
||||||
},
|
},
|
||||||
|
|
@ -1108,14 +1025,14 @@
|
||||||
"name": "Overcharge",
|
"name": "Overcharge",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Before rolling damage for the Box’s attack, you can <strong>mark a Stress</strong> to add a <strong>d6</strong> to the damage roll. Additionally, you gain a Fear.</p>",
|
"description": "<p>Before rolling damage for the @Lookup[@name]’s attack, you can <strong>mark a Stress</strong> to add a <strong>d6</strong> to the damage roll. Additionally, you gain a Fear.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"3XOvKoYz4CqMNrU9": {
|
"3XOvKoYz4CqMNrU9": {
|
||||||
"type": "healing",
|
"type": "healing",
|
||||||
"_id": "3XOvKoYz4CqMNrU9",
|
"_id": "3XOvKoYz4CqMNrU9",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Before rolling damage for the Box’s attack, you can <strong>mark a Stress</strong> to add a <strong>d6</strong> to the damage roll. Additionally, you gain a Fear.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -1187,7 +1104,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"_id": "ITzpRJr2jWK0Ksmp",
|
"_id": "ITzpRJr2jWK0Ksmp",
|
||||||
"img": "icons/creatures/magical/construct-golem-stone-blue.webp",
|
"img": "icons/creatures/magical/construct-golem-stone-blue.webp",
|
||||||
|
|
@ -1200,15 +1118,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.4",
|
|
||||||
"createdTime": 1754074647690,
|
|
||||||
"modifiedTime": 1755264742627,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.ITzpRJr2jWK0Ksmp"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.ITzpRJr2jWK0Ksmp"
|
||||||
},
|
},
|
||||||
|
|
@ -1216,14 +1126,14 @@
|
||||||
"name": "Death Quake",
|
"name": "Death Quake",
|
||||||
"type": "feature",
|
"type": "feature",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>When the Box marks their last HP, the magic powering them ruptures in an explosion of force. All targets within Close range must succeed on an Instinct Reaction Roll or take <strong>2d8+1</strong> magic damage</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "<p>When the @Lookup[@name] marks their last HP, the magic powering them ruptures in an explosion of force. All targets within Close range must succeed on an Instinct Reaction Roll or take <strong>2d8+1</strong> magic damage</p><p>@Template[type:emanation|range:c]</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"oCpv4zi9jtEpo0K1": {
|
"oCpv4zi9jtEpo0K1": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "oCpv4zi9jtEpo0K1",
|
"_id": "oCpv4zi9jtEpo0K1",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>When the Box marks their last HP, the magic powering them ruptures in an explosion of force. All targets within Close range must succeed on an Instinct Reaction Roll or take <strong>2d8+1</strong> magic damage</p><p>@Template[type:emanation|range:c]</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -1294,7 +1204,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"_id": "YvfzPyJbbv2ia6Yp",
|
"_id": "YvfzPyJbbv2ia6Yp",
|
||||||
"img": "icons/magic/sonic/explosion-shock-wave-teal.webp",
|
"img": "icons/magic/sonic/explosion-shock-wave-teal.webp",
|
||||||
|
|
@ -1307,15 +1218,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754074737929,
|
|
||||||
"modifiedTime": 1754142216691,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!dgH3fW9FTYLaIDvS.YvfzPyJbbv2ia6Yp"
|
"_key": "!actors.items!dgH3fW9FTYLaIDvS.YvfzPyJbbv2ia6Yp"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,20 +122,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 75,
|
"page": 75,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "large"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784226,
|
|
||||||
"modifiedTime": 1755384265295,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "71qKDLKO3CsrNkdy",
|
"_id": "71qKDLKO3CsrNkdy",
|
||||||
"sort": 1200000,
|
"sort": 1200000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -244,7 +234,7 @@
|
||||||
"_id": "2fXzhh2qil8dw3vw",
|
"_id": "2fXzhh2qil8dw3vw",
|
||||||
"img": "icons/skills/melee/strike-slashes-orange.webp",
|
"img": "icons/skills/melee/strike-slashes-orange.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>Targets who mark HP from the Bear’s standard attack are knocked back to Very Close range.</p>",
|
"description": "<p>Targets who mark HP from the @Lookup[@name]’s standard attack are knocked back to Very Close range.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {},
|
"actions": {},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
|
|
@ -260,15 +250,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754012151208,
|
|
||||||
"modifiedTime": 1754012182512,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!71qKDLKO3CsrNkdy.2fXzhh2qil8dw3vw"
|
"_key": "!actors.items!71qKDLKO3CsrNkdy.2fXzhh2qil8dw3vw"
|
||||||
},
|
},
|
||||||
|
|
@ -285,7 +267,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "PXL3e51eBYZ4O2lb",
|
"_id": "PXL3e51eBYZ4O2lb",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p><strong>Mark a Stress</strong> to make an attack against a target within Melee range. On a success, deal <strong>3d4+10</strong> physical damage and the target is <em>Restrained</em> until they break free with a successful Strength Roll.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [
|
"cost": [
|
||||||
|
|
@ -369,7 +351,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
|
|
@ -406,15 +389,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754012285077,
|
|
||||||
"modifiedTime": 1754012313771,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!71qKDLKO3CsrNkdy.zgR0MEqyobKp2yXr.U50Ccm9emMqAxma6"
|
"_key": "!actors.items.effects!71qKDLKO3CsrNkdy.zgR0MEqyobKp2yXr.U50Ccm9emMqAxma6"
|
||||||
}
|
}
|
||||||
|
|
@ -427,15 +402,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754012195973,
|
|
||||||
"modifiedTime": 1754143721840,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!71qKDLKO3CsrNkdy.zgR0MEqyobKp2yXr"
|
"_key": "!actors.items!71qKDLKO3CsrNkdy.zgR0MEqyobKp2yXr"
|
||||||
},
|
},
|
||||||
|
|
@ -520,7 +487,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "reaction"
|
||||||
},
|
},
|
||||||
"effects": [],
|
"effects": [],
|
||||||
"folder": null,
|
"folder": null,
|
||||||
|
|
@ -531,15 +499,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1754012330113,
|
|
||||||
"modifiedTime": 1760209165515,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!71qKDLKO3CsrNkdy.4hJbq9WCwJn78frt"
|
"_key": "!actors.items!71qKDLKO3CsrNkdy.4hJbq9WCwJn78frt"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,20 +117,10 @@
|
||||||
"source": "Daggerheart SRD",
|
"source": "Daggerheart SRD",
|
||||||
"page": 77,
|
"page": 77,
|
||||||
"artist": ""
|
"artist": ""
|
||||||
}
|
},
|
||||||
|
"size": "medium"
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
|
||||||
"compendiumSource": null,
|
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.347",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.0.5",
|
|
||||||
"createdTime": 1753922784226,
|
|
||||||
"modifiedTime": 1755384320981,
|
|
||||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
|
||||||
},
|
|
||||||
"_id": "B4LZcGuBAHzyVdzy",
|
"_id": "B4LZcGuBAHzyVdzy",
|
||||||
"sort": 2000000,
|
"sort": 2000000,
|
||||||
"ownership": {
|
"ownership": {
|
||||||
|
|
@ -239,14 +229,14 @@
|
||||||
"_id": "qEn4baWgkjKtmILp",
|
"_id": "qEn4baWgkjKtmILp",
|
||||||
"img": "icons/equipment/shield/shield-round-boss-wood-brown.webp",
|
"img": "icons/equipment/shield/shield-round-boss-wood-brown.webp",
|
||||||
"system": {
|
"system": {
|
||||||
"description": "<p>A creature who tries to move within Very Close range of the Guard must succeed on an Agility Roll. If additional Bladed Guards are standing in a line alongside the first, and each is within Melee range of another guard in the line, the Difficulty increases by the total number of guards in that line.</p>",
|
"description": "<p>A creature who tries to move within Very Close range of the @Lookup[@name] must succeed on an Agility Roll. If additional @Lookup[@name]s are standing in a line alongside the first, and each is within Melee range of another guard in the line, the Difficulty increases by the total number of guards in that line.</p>",
|
||||||
"resource": null,
|
"resource": null,
|
||||||
"actions": {
|
"actions": {
|
||||||
"3lbeEeJdjzPn0MoG": {
|
"3lbeEeJdjzPn0MoG": {
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "3lbeEeJdjzPn0MoG",
|
"_id": "3lbeEeJdjzPn0MoG",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>A creature who tries to move within Very Close range of the Guard must succeed on an Agility Roll. If additional Bladed Guards are standing in a line alongside the first, and each is within Melee range of another guard in the line, the Difficulty increases by the total number of guards in that line.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "passive",
|
"actionType": "passive",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -302,15 +292,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.350",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "1.1.2",
|
|
||||||
"createdTime": 1754012824140,
|
|
||||||
"modifiedTime": 1760021165250,
|
|
||||||
"lastModifiedBy": "fBcTgyTzoARBvohY"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!B4LZcGuBAHzyVdzy.qEn4baWgkjKtmILp"
|
"_key": "!actors.items!B4LZcGuBAHzyVdzy.qEn4baWgkjKtmILp"
|
||||||
},
|
},
|
||||||
|
|
@ -327,7 +309,7 @@
|
||||||
"type": "attack",
|
"type": "attack",
|
||||||
"_id": "TK5R00afB1RIA6gp",
|
"_id": "TK5R00afB1RIA6gp",
|
||||||
"systemPath": "actions",
|
"systemPath": "actions",
|
||||||
"description": "<p>Make an attack against a target within Very Close range. On a success, <strong>mark a Stress</strong> to Restrain the target until they break free with a successful attack, Finesse Roll, or Strength Roll.</p>",
|
"description": "",
|
||||||
"chatDisplay": true,
|
"chatDisplay": true,
|
||||||
"actionType": "action",
|
"actionType": "action",
|
||||||
"cost": [],
|
"cost": [],
|
||||||
|
|
@ -377,7 +359,8 @@
|
||||||
},
|
},
|
||||||
"originItemType": null,
|
"originItemType": null,
|
||||||
"subType": null,
|
"subType": null,
|
||||||
"originId": null
|
"originId": null,
|
||||||
|
"featureForm": "action"
|
||||||
},
|
},
|
||||||
"effects": [
|
"effects": [
|
||||||
{
|
{
|
||||||
|
|
@ -414,15 +397,7 @@
|
||||||
"sort": 0,
|
"sort": 0,
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754013054188,
|
|
||||||
"modifiedTime": 1754013085395,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items.effects!B4LZcGuBAHzyVdzy.9gizFt9ovKL05DXu.LmzztuktRkwOCy1a"
|
"_key": "!actors.items.effects!B4LZcGuBAHzyVdzy.9gizFt9ovKL05DXu.LmzztuktRkwOCy1a"
|
||||||
}
|
}
|
||||||
|
|
@ -435,15 +410,7 @@
|
||||||
},
|
},
|
||||||
"flags": {},
|
"flags": {},
|
||||||
"_stats": {
|
"_stats": {
|
||||||
"compendiumSource": null,
|
"compendiumSource": null
|
||||||
"duplicateSource": null,
|
|
||||||
"exportSource": null,
|
|
||||||
"coreVersion": "13.346",
|
|
||||||
"systemId": "daggerheart",
|
|
||||||
"systemVersion": "0.0.1",
|
|
||||||
"createdTime": 1754012944888,
|
|
||||||
"modifiedTime": 1754143803011,
|
|
||||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
|
||||||
},
|
},
|
||||||
"_key": "!actors.items!B4LZcGuBAHzyVdzy.9gizFt9ovKL05DXu"
|
"_key": "!actors.items!B4LZcGuBAHzyVdzy.9gizFt9ovKL05DXu"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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