mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 19:51:08 +01:00
Compare commits
124 commits
| 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 | ||
|
|
b4c2034789 | ||
|
|
cd5b8e9c75 | ||
|
|
44b805d0df | ||
|
|
d616ddc113 | ||
|
|
4510deae96 | ||
|
|
d137e33c3d | ||
|
|
9e0bc3cff1 | ||
|
|
f78f8e32b6 | ||
|
|
d5b501cb98 | ||
|
|
4b76223e45 | ||
|
|
b9508e19e8 | ||
|
|
e6a242ba43 | ||
|
|
81e7f24d22 | ||
|
|
6398ba15f2 | ||
|
|
ebf98a6ae5 | ||
|
|
2920ead81a | ||
|
|
9f7554cdff | ||
|
|
87643dc662 | ||
|
|
e77f538ab7 |
1399 changed files with 12777 additions and 24723 deletions
314
daggerheart.mjs
314
daggerheart.mjs
|
|
@ -17,12 +17,64 @@ 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';
|
||||||
|
|
||||||
|
CONFIG.DH = SYSTEM;
|
||||||
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
|
|
||||||
|
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
||||||
|
CONFIG.Dice.daggerheart = {
|
||||||
|
DHRoll: DHRoll,
|
||||||
|
DualityRoll: DualityRoll,
|
||||||
|
D20Roll: D20Roll,
|
||||||
|
DamageRoll: DamageRoll
|
||||||
|
};
|
||||||
|
|
||||||
|
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||||
|
CONFIG.Actor.dataModels = models.actors.config;
|
||||||
|
|
||||||
|
CONFIG.Item.documentClass = documents.DHItem;
|
||||||
|
CONFIG.Item.dataModels = models.items.config;
|
||||||
|
|
||||||
|
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||||
|
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||||
|
|
||||||
|
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||||
|
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||||
|
CONFIG.Combatant.documentClass = documents.DHCombatant;
|
||||||
|
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
|
||||||
|
|
||||||
|
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
||||||
|
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||||
|
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
||||||
|
|
||||||
|
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||||
|
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||||
|
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||||
|
|
||||||
|
CONFIG.Scene.documentClass = documents.DhScene;
|
||||||
|
|
||||||
|
CONFIG.Token.documentClass = documents.DhToken;
|
||||||
|
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||||
|
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||||
|
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||||
|
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||||
|
|
||||||
|
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||||
|
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||||
|
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
|
||||||
|
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||||
|
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
|
||||||
|
CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
|
||||||
|
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
|
||||||
|
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||||
|
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||||
|
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
||||||
|
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
||||||
|
CONFIG.ux.TemplateManager = new TemplateManager();
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
Hooks.once('init', () => {
|
||||||
CONFIG.DH = SYSTEM;
|
|
||||||
game.system.api = {
|
game.system.api = {
|
||||||
applications,
|
applications,
|
||||||
data,
|
data,
|
||||||
|
|
@ -32,67 +84,102 @@ Hooks.once('init', () => {
|
||||||
fields
|
fields
|
||||||
};
|
};
|
||||||
|
|
||||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
|
||||||
|
|
||||||
CONFIG.Dice.daggerheart = {
|
|
||||||
DHRoll: DHRoll,
|
|
||||||
DualityRoll: DualityRoll,
|
|
||||||
D20Roll: D20Roll,
|
|
||||||
DamageRoll: DamageRoll
|
|
||||||
};
|
|
||||||
|
|
||||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
|
||||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
|
||||||
|
|
||||||
const { DocumentSheetConfig } = foundry.applications.apps;
|
const { DocumentSheetConfig } = foundry.applications.apps;
|
||||||
CONFIG.Token.documentClass = documents.DhToken;
|
|
||||||
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
|
||||||
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
||||||
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
||||||
makeDefault: true
|
makeDefault: true
|
||||||
});
|
});
|
||||||
|
|
||||||
CONFIG.Item.documentClass = documents.DHItem;
|
const sheetLabel = typePath => () =>
|
||||||
|
game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
|
||||||
//Registering the Item DataModel
|
type: game.i18n.localize(typePath)
|
||||||
CONFIG.Item.dataModels = models.items.config;
|
});
|
||||||
|
|
||||||
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 });
|
|
||||||
|
|
||||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
|
||||||
CONFIG.Actor.dataModels = models.actors.config;
|
|
||||||
|
|
||||||
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')
|
||||||
});
|
});
|
||||||
|
|
||||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
|
||||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
|
||||||
|
|
||||||
DocumentSheetConfig.unregisterSheet(
|
DocumentSheetConfig.unregisterSheet(
|
||||||
CONFIG.ActiveEffect.documentClass,
|
CONFIG.ActiveEffect.documentClass,
|
||||||
'core',
|
'core',
|
||||||
|
|
@ -103,50 +190,20 @@ Hooks.once('init', () => {
|
||||||
SYSTEM.id,
|
SYSTEM.id,
|
||||||
applications.sheetConfigs.ActiveEffectConfig,
|
applications.sheetConfigs.ActiveEffectConfig,
|
||||||
{
|
{
|
||||||
makeDefault: true
|
makeDefault: true,
|
||||||
|
label: sheetLabel('DOCUMENT.ActiveEffect')
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
|
||||||
|
|
||||||
CONFIG.Combat.dataModels = {
|
|
||||||
base: models.DhCombat
|
|
||||||
};
|
|
||||||
|
|
||||||
CONFIG.Combatant.dataModels = {
|
|
||||||
base: models.DhCombatant
|
|
||||||
};
|
|
||||||
|
|
||||||
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
|
||||||
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
|
||||||
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
|
||||||
|
|
||||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
|
||||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
|
||||||
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
|
||||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
|
||||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
|
||||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
|
||||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
|
||||||
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
|
|
||||||
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
|
|
||||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
|
||||||
|
|
||||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
|
||||||
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
|
||||||
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
|
||||||
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
|
||||||
|
|
||||||
CONFIG.ux.TemplateManager = new TemplateManager();
|
|
||||||
|
|
||||||
game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent);
|
game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent);
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
@ -176,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)) {
|
||||||
|
|
@ -196,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));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -251,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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
156
lang/en.json
156
lang/en.json
|
|
@ -36,6 +36,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"DAGGERHEART": {
|
"DAGGERHEART": {
|
||||||
|
"CharacterSheet": "Character Sheet",
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
"attack": {
|
"attack": {
|
||||||
|
|
@ -103,6 +104,7 @@
|
||||||
"Settings": {
|
"Settings": {
|
||||||
"attackBonus": "Attack Bonus",
|
"attackBonus": "Attack Bonus",
|
||||||
"attackName": "Attack Name",
|
"attackName": "Attack Name",
|
||||||
|
"criticalThreshold": "Critical Threshold",
|
||||||
"includeBase": { "label": "Include Item Damage" },
|
"includeBase": { "label": "Include Item Damage" },
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||||
|
|
@ -130,7 +132,8 @@
|
||||||
"RangeDependance": {
|
"RangeDependance": {
|
||||||
"hint": "Settings for an optional distance at which this effect should activate",
|
"hint": "Settings for an optional distance at which this effect should activate",
|
||||||
"title": "Range Dependant"
|
"title": "Range Dependant"
|
||||||
}
|
},
|
||||||
|
"immuneStatusText": "Immunity: {status}"
|
||||||
},
|
},
|
||||||
"ACTORS": {
|
"ACTORS": {
|
||||||
"Adversary": {
|
"Adversary": {
|
||||||
|
|
@ -223,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"
|
||||||
|
|
@ -323,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"
|
||||||
|
|
@ -342,7 +347,8 @@
|
||||||
"progress": {
|
"progress": {
|
||||||
"current": { "label": "Current" },
|
"current": { "label": "Current" },
|
||||||
"looping": { "label": "Looping" },
|
"looping": { "label": "Looping" },
|
||||||
"max": { "label": "Max" },
|
"start": { "label": "Start" },
|
||||||
|
"startFormula": { "label": "Start Formula" },
|
||||||
"type": {
|
"type": {
|
||||||
"label": { "label": "Label", "hint": "Used for custom" },
|
"label": { "label": "Label", "hint": "Used for custom" },
|
||||||
"value": { "label": "Value" }
|
"value": { "label": "Value" }
|
||||||
|
|
@ -370,19 +376,21 @@
|
||||||
"newCountdown": "New Countdown",
|
"newCountdown": "New Countdown",
|
||||||
"removeCountdownTitle": "Remove Countdown",
|
"removeCountdownTitle": "Remove Countdown",
|
||||||
"removeCountdownText": "Are you sure you want to remove the countdown: {name}?",
|
"removeCountdownText": "Are you sure you want to remove the countdown: {name}?",
|
||||||
"current": "Current",
|
"current": "Current Value",
|
||||||
"max": "Max",
|
"start": "Start Value",
|
||||||
"currentCountdownValue": "Current: {value}",
|
"startFormula": "Randomized Start Value Formula",
|
||||||
"currentCountdownMax": "Max: {value}",
|
"currentCountdownCurrent": "Current: {value}",
|
||||||
|
"currentCountdownStart": "Start: {value}",
|
||||||
"category": "Category",
|
"category": "Category",
|
||||||
"progressionType": "Progression Type",
|
"progressionType": "Progression",
|
||||||
"decreasing": "Decreasing",
|
"decreasing": "Decreasing",
|
||||||
"looping": "Looping",
|
"looping": "Looping",
|
||||||
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
||||||
"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}",
|
||||||
|
|
@ -396,7 +404,8 @@
|
||||||
"stressReduction": "Reduce By Stress",
|
"stressReduction": "Reduce By Stress",
|
||||||
"title": "Damage Reduction",
|
"title": "Damage Reduction",
|
||||||
"unncessaryStress": "You don't need to expend stress",
|
"unncessaryStress": "You don't need to expend stress",
|
||||||
"usedMarks": "Used Marks"
|
"usedMarks": "Used Marks",
|
||||||
|
"reduceSeverity": "Severity Reduced By {nr}"
|
||||||
},
|
},
|
||||||
"DeathMove": {
|
"DeathMove": {
|
||||||
"selectMove": "Select Move",
|
"selectMove": "Select Move",
|
||||||
|
|
@ -464,6 +473,9 @@
|
||||||
"title": "Select Image",
|
"title": "Select Image",
|
||||||
"selectImage": "Select Image"
|
"selectImage": "Select Image"
|
||||||
},
|
},
|
||||||
|
"ItemTransfer": {
|
||||||
|
"transfer": "Transfer"
|
||||||
|
},
|
||||||
"Levelup": {
|
"Levelup": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"creatureComfort": {
|
"creatureComfort": {
|
||||||
|
|
@ -496,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.",
|
||||||
|
|
@ -575,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": {
|
||||||
|
|
@ -601,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": {
|
||||||
|
|
@ -610,11 +627,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"ActionType": {
|
|
||||||
"passive": "Passive",
|
|
||||||
"action": "Action",
|
|
||||||
"reaction": "Reaction"
|
|
||||||
},
|
|
||||||
"AdversaryTrait": {
|
"AdversaryTrait": {
|
||||||
"relentless": {
|
"relentless": {
|
||||||
"name": "Relentless",
|
"name": "Relentless",
|
||||||
|
|
@ -674,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",
|
||||||
|
|
@ -897,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"
|
||||||
|
|
@ -991,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",
|
||||||
|
|
@ -1031,7 +1081,8 @@
|
||||||
},
|
},
|
||||||
"ItemResourceType": {
|
"ItemResourceType": {
|
||||||
"simple": "Simple",
|
"simple": "Simple",
|
||||||
"diceValue": "Dice Value"
|
"diceValue": "Dice Value",
|
||||||
|
"die": "Die"
|
||||||
},
|
},
|
||||||
"Range": {
|
"Range": {
|
||||||
"self": {
|
"self": {
|
||||||
|
|
@ -1103,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",
|
||||||
|
|
@ -1212,7 +1271,7 @@
|
||||||
},
|
},
|
||||||
"burning": {
|
"burning": {
|
||||||
"name": "Burning",
|
"name": "Burning",
|
||||||
"description": "When you roll the maximum value on a damage die, roll an additional damage die.",
|
"description": "When you roll a 6 on a damage die, the target must mark a Stress.",
|
||||||
"actions": {
|
"actions": {
|
||||||
"burn": {
|
"burn": {
|
||||||
"name": "Burn",
|
"name": "Burn",
|
||||||
|
|
@ -1743,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"
|
||||||
|
|
@ -1758,6 +1819,7 @@
|
||||||
"plural": "Costs"
|
"plural": "Costs"
|
||||||
},
|
},
|
||||||
"Damage": {
|
"Damage": {
|
||||||
|
"massive": "Massive",
|
||||||
"severe": "Severe",
|
"severe": "Severe",
|
||||||
"major": "Major",
|
"major": "Major",
|
||||||
"minor": "Minor",
|
"minor": "Minor",
|
||||||
|
|
@ -2023,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",
|
||||||
|
|
@ -2049,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",
|
||||||
|
|
@ -2063,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",
|
||||||
|
|
@ -2084,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",
|
||||||
|
|
@ -2098,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",
|
||||||
|
|
@ -2118,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",
|
||||||
|
|
@ -2174,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" }
|
||||||
},
|
},
|
||||||
|
|
@ -2197,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",
|
||||||
|
|
@ -2373,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",
|
||||||
|
|
@ -2391,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"
|
||||||
},
|
},
|
||||||
|
|
@ -2445,7 +2522,8 @@
|
||||||
"texture": "Texture",
|
"texture": "Texture",
|
||||||
"colorset": "Theme",
|
"colorset": "Theme",
|
||||||
"material": "Material",
|
"material": "Material",
|
||||||
"system": "Dice Preset"
|
"system": "Dice Preset",
|
||||||
|
"font": "Font"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"variantRules": {
|
"variantRules": {
|
||||||
|
|
@ -2454,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": {
|
||||||
|
|
@ -2467,6 +2550,10 @@
|
||||||
"actionTokens": {
|
"actionTokens": {
|
||||||
"enabled": { "label": "Enabled" },
|
"enabled": { "label": "Enabled" },
|
||||||
"tokens": { "label": "Tokens" }
|
"tokens": { "label": "Tokens" }
|
||||||
|
},
|
||||||
|
"massiveDamage": {
|
||||||
|
"title": "Massive Damage",
|
||||||
|
"enabled": { "label": "Enabled" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2526,7 +2613,8 @@
|
||||||
"abilityCheckTitle": "{ability} Check"
|
"abilityCheckTitle": "{ability} Check"
|
||||||
},
|
},
|
||||||
"effectSummary": {
|
"effectSummary": {
|
||||||
"title": "Effects Applied"
|
"title": "Effects Applied",
|
||||||
|
"immunityTo": "Immunity: {immunities}"
|
||||||
},
|
},
|
||||||
"featureTitle": "Class Feature",
|
"featureTitle": "Class Feature",
|
||||||
"groupRoll": {
|
"groupRoll": {
|
||||||
|
|
@ -2538,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}",
|
||||||
|
|
@ -2574,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",
|
||||||
|
|
@ -2650,7 +2743,7 @@
|
||||||
"cardTooHighLevel": "The card is too high level!",
|
"cardTooHighLevel": "The card is too high level!",
|
||||||
"duplicateCard": "You cannot select the same card more than once.",
|
"duplicateCard": "You cannot select the same card more than once.",
|
||||||
"duplicateCharacter": "This actor is already registered in the party members list.",
|
"duplicateCharacter": "This actor is already registered in the party members list.",
|
||||||
"onlyCharactersInPartySheet": "You can only drag characters, companions and adverasries to the party sheet.",
|
"onlyCharactersInPartySheet": "You can only drag characters, companions and adversaries to the party sheet.",
|
||||||
"notPrimary": "The weapon is not a primary weapon!",
|
"notPrimary": "The weapon is not a primary weapon!",
|
||||||
"notSecondary": "The weapon is not a secondary weapon!",
|
"notSecondary": "The weapon is not a secondary weapon!",
|
||||||
"itemTooHighTier": "The item must be from Tier1",
|
"itemTooHighTier": "The item must be from Tier1",
|
||||||
|
|
@ -2688,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",
|
||||||
|
|
@ -2724,7 +2825,10 @@
|
||||||
"rightClickExtand": "Right-Click to extand",
|
"rightClickExtand": "Right-Click to extand",
|
||||||
"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",
|
||||||
|
"middleClick": "[Middle Click] Keep tooltip view",
|
||||||
|
"tokenSize": "The token size used on the canvas"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
||||||
export { default as DeathMove } from './deathMove.mjs';
|
export { default as DeathMove } from './deathMove.mjs';
|
||||||
export { default as Downtime } from './downtime.mjs';
|
export { default as Downtime } from './downtime.mjs';
|
||||||
export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
|
export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
|
||||||
|
export { default as ItemTransferDialog } from './itemTransfer.mjs';
|
||||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||||
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,11 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _prepareContext(options) {
|
async _prepareContext(options) {
|
||||||
const actions = this.#item.system.actionsList,
|
const actions = this.#item.system.actionsList.map(action => ({
|
||||||
|
...action.toObject(),
|
||||||
|
id: action.id,
|
||||||
|
img: action.baseAction ? action.parent.parent.img : action.img
|
||||||
|
})),
|
||||||
itemName = this.#item.name;
|
itemName = this.#item.name;
|
||||||
return {
|
return {
|
||||||
...(await super._prepareContext(options)),
|
...(await super._prepareContext(options)),
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.reject = reject;
|
this.reject = reject;
|
||||||
this.actor = actor;
|
this.actor = actor;
|
||||||
this.damage = damage;
|
this.damage = damage;
|
||||||
|
this.damageType = damageType;
|
||||||
this.rulesDefault = game.settings.get(
|
this.rulesDefault = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||||
|
|
@ -57,6 +58,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.reduceSeverity = this.damageType.reduce((value, curr) => {
|
||||||
|
return Math.max(this.actor.system.rules.damageReduction.reduceSeverity[curr], value);
|
||||||
|
}, 0);
|
||||||
|
this.actor.system.rules.damageReduction.reduceSeverity[this.damageType];
|
||||||
|
|
||||||
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
|
this.thresholdImmunities = Object.keys(actor.system.rules.damageReduction.thresholdImmunities).reduce(
|
||||||
(acc, key) => {
|
(acc, key) => {
|
||||||
if (actor.system.rules.damageReduction.thresholdImmunities[key])
|
if (actor.system.rules.damageReduction.thresholdImmunities[key])
|
||||||
|
|
@ -111,7 +117,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
|
CONFIG.DH.GENERAL.ruleChoice.onWithToggle.id,
|
||||||
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
|
CONFIG.DH.GENERAL.ruleChoice.offWithToggle.id
|
||||||
].includes(this.rulesDefault);
|
].includes(this.rulesDefault);
|
||||||
context.thresholdImmunities = this.thresholdImmunities;
|
context.reduceSeverity = this.reduceSeverity;
|
||||||
|
context.thresholdImmunities =
|
||||||
|
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
|
||||||
|
|
||||||
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } =
|
||||||
this.getDamageInfo();
|
this.getDamageInfo();
|
||||||
|
|
@ -173,6 +181,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
||||||
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
if (this.reduceSeverity) {
|
||||||
|
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
62
module/applications/dialogs/itemTransfer.mjs
Normal file
62
module/applications/dialogs/itemTransfer.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(data) {
|
||||||
|
super({});
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
return this.data.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
|
||||||
|
position: { width: 400, height: 'auto' },
|
||||||
|
window: { icon: 'fa-solid fa-hand-holding-hand' },
|
||||||
|
actions: {
|
||||||
|
finish: ItemTransferDialog.#finish
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true }
|
||||||
|
};
|
||||||
|
|
||||||
|
async _prepareContext(_options) {
|
||||||
|
const context = await super._prepareContext(_options);
|
||||||
|
return foundry.utils.mergeObject(context, this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #finish() {
|
||||||
|
this.selected = this.form.elements.quantity.valueAsNumber || null;
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static #determineTransferOptions({ originActor, targetActor, item, currency }) {
|
||||||
|
originActor ??= item?.actor;
|
||||||
|
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||||
|
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||||
|
|
||||||
|
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(options) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const data = this.#determineTransferOptions(options);
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,9 +32,13 @@ 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) acc[key] = effect;
|
if (effect.systemEffect) {
|
||||||
|
const disabled = !effect.isActive && this.actor.system.rules?.conditionImmunities?.[key];
|
||||||
|
acc[key] = { ...effect, disabled };
|
||||||
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
@ -55,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);
|
||||||
}
|
}
|
||||||
|
|
@ -190,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;
|
||||||
}, []),
|
}, []),
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
context.diceSoNiceSystems = Object.fromEntries(
|
context.diceSoNiceSystems = Object.fromEntries(
|
||||||
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
|
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
|
||||||
);
|
);
|
||||||
|
context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList();
|
||||||
|
|
||||||
foundry.utils.mergeObject(
|
foundry.utils.mergeObject(
|
||||||
context.dsnTabs,
|
context.dsnTabs,
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils
|
||||||
|
|
||||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||||
|
|
||||||
const { TextEditor } = foundry.applications.ux;
|
|
||||||
export default class CharacterSheet extends DHBaseActorSheet {
|
export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
|
|
@ -31,8 +30,10 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||||
|
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,15 +140,15 @@ 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 => {
|
||||||
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
htmlElement.querySelectorAll('.item-resource.die').forEach(element => {
|
||||||
|
element.addEventListener('contextmenu', this.lowerResourceDie.bind(this));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
|
|
@ -210,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,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',
|
||||||
|
|
@ -615,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;
|
||||||
|
|
@ -709,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
|
||||||
|
|
@ -858,6 +863,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
static #advanceResourceDie(_, target) {
|
||||||
|
this.updateResourceDie(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
lowerResourceDie(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.updateResourceDie(event.target, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateResourceDie(target, advance) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const advancedValue = item.system.resource.value + (advance ? 1 : -1);
|
||||||
|
await item.update({
|
||||||
|
'system.resource.value': Math.min(advancedValue, Number(item.system.resource.dieFaces.split('d')[1]))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
@ -867,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}
|
||||||
|
|
@ -877,34 +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);
|
||||||
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();
|
|
||||||
|
|
||||||
super._onDrop(event);
|
|
||||||
this._onDropItem(event, TextEditor.getDragEventData(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type === 'beastform') {
|
if (item.type === 'beastform') {
|
||||||
|
|
@ -914,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: '.action-section .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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
import DHItem from '../../../documents/item.mjs';
|
import DHItem from '../../../documents/item.mjs';
|
||||||
import DhParty from '../../../data/actor/party.mjs';
|
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -21,7 +20,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
classes: ['party'],
|
classes: ['party'],
|
||||||
position: {
|
position: {
|
||||||
width: 550,
|
width: 550,
|
||||||
height: 900,
|
height: 900
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true
|
||||||
|
|
@ -41,7 +40,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||||
refreshActors: DaggerheartMenu.refreshActors
|
refreshActors: DaggerheartMenu.refreshActors
|
||||||
},
|
},
|
||||||
dragDrop: [{ dragSelector: '.actors-section .inventory-item', dropSelector: null }]
|
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||||
};
|
};
|
||||||
|
|
||||||
/**@override */
|
/**@override */
|
||||||
|
|
@ -94,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) {
|
||||||
|
|
@ -440,24 +420,8 @@ export default class Party extends DHBaseActorSheet {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _onDragStart(event) {
|
async _onDropActor(event, document) {
|
||||||
const item = event.currentTarget.closest('.inventory-item');
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
|
||||||
event.dataTransfer.setDragImage(item, 60, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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)) {
|
||||||
|
|
@ -465,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,3 +1,4 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
|
@ -33,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 }
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -68,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,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);
|
||||||
});
|
});
|
||||||
|
|
@ -149,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 */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -217,13 +257,98 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
||||||
/* Application Drag/Drop */
|
/* Application Drag/Drop */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
async _onDrop(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
|
if (data.type === 'Currency' && ['character', 'party'].includes(this.document.type)) {
|
||||||
|
const originActor = await foundry.utils.fromUuid(data.originActor);
|
||||||
|
if (!originActor || originActor.uuid === this.document.uuid) return;
|
||||||
|
const currency = data.currency;
|
||||||
|
const quantity = await game.system.api.applications.dialogs.ItemTransferDialog.configure({
|
||||||
|
originActor,
|
||||||
|
targetActor: this.document,
|
||||||
|
currency
|
||||||
|
});
|
||||||
|
if (quantity) {
|
||||||
|
originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) });
|
||||||
|
this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On dragStart on the item.
|
* On dragStart on the item.
|
||||||
* @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',
|
||||||
|
|
@ -233,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
removeResource: DHBaseItemSheet.#removeResource
|
removeResource: DHBaseItemSheet.#removeResource
|
||||||
},
|
},
|
||||||
dragDrop: [
|
dragDrop: [
|
||||||
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
|
{ dragSelector: null, dropSelector: '.drop-section' },
|
||||||
{ dragSelector: '.feature-item', dropSelector: null },
|
{ dragSelector: '.feature-item', dropSelector: null },
|
||||||
{ dragSelector: '.action-item', dropSelector: null }
|
{ dragSelector: '.inventory-item', dropSelector: null }
|
||||||
],
|
],
|
||||||
contextMenus: [
|
contextMenus: [
|
||||||
{
|
{
|
||||||
|
|
@ -199,18 +199,32 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
static async #deleteFeature(_, element) {
|
static async #deleteFeature(_, element) {
|
||||||
const target = element.closest('[data-item-uuid]');
|
const target = element.closest('[data-item-uuid]');
|
||||||
const feature = await getDocFromElement(target);
|
const feature = await getDocFromElement(target);
|
||||||
|
|
||||||
if (!feature) {
|
if (!feature) {
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.features': this.document.system.features
|
'system.features': this.document.system.features
|
||||||
.filter(x => x.item)
|
.filter(x => x.item)
|
||||||
.map(x => ({ ...x, item: x.item.uuid }))
|
.map(x => ({ ...x, item: x.item.uuid }))
|
||||||
});
|
});
|
||||||
} else
|
} else {
|
||||||
|
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||||
|
window: {
|
||||||
|
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||||
|
type: game.i18n.localize('TYPES.Item.feature'),
|
||||||
|
name: feature.name
|
||||||
|
})
|
||||||
|
},
|
||||||
|
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!confirmed) return;
|
||||||
|
|
||||||
await this.document.update({
|
await this.document.update({
|
||||||
'system.features': this.document.system.features
|
'system.features': this.document.system.features
|
||||||
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
|
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
|
||||||
.map(x => ({ ...x, item: x.item.uuid }))
|
.map(x => ({ ...x, item: x.item.uuid }))
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -242,37 +256,30 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
* @param {DragEvent} event - The drag event
|
* @param {DragEvent} event - The drag event
|
||||||
*/
|
*/
|
||||||
async _onDragStart(event) {
|
async _onDragStart(event) {
|
||||||
|
/* Can prolly be improved a lot, but I don't wanna >_< */
|
||||||
const featureItem = event.currentTarget.closest('.feature-item');
|
const featureItem = event.currentTarget.closest('.feature-item');
|
||||||
|
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||||
|
const lineItem = event.currentTarget.closest('.item-line');
|
||||||
|
const dragItemData = featureItem ?? inventoryItem ?? lineItem;
|
||||||
|
|
||||||
if (featureItem) {
|
const dragItem = await foundry.utils.fromUuid(dragItemData.dataset.itemUuid);
|
||||||
const feature = this.document.system.features.find(x => x?.id === featureItem.id);
|
if (dragItem) {
|
||||||
if (!feature) {
|
if (!dragItem) {
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
let dragData = {};
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
if (dragItemData.dataset.type === 'effect')
|
||||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
dragData = {
|
||||||
} else {
|
type: 'ActiveEffect',
|
||||||
const actionItem = event.currentTarget.closest('.action-item');
|
fromInternal: this.document.uuid,
|
||||||
if (actionItem) {
|
data: { ...dragItem, uuid: dragItem.uuid, id: dragItem.id }
|
||||||
const action = this.document.system.actions[actionItem.dataset.index];
|
|
||||||
if (!action) {
|
|
||||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionData = {
|
|
||||||
type: 'Action',
|
|
||||||
data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid },
|
|
||||||
fromInternal: true
|
|
||||||
};
|
};
|
||||||
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
|
else dragData = { type: 'Item', uuid: dragItem.uuid, id: dragItem.id, fromInternal: this.document.id };
|
||||||
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
|
|
||||||
} else {
|
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||||
super._onDragStart(event);
|
event.dataTransfer.setDragImage(dragItemData.querySelector('img'), 60, 0);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +291,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
super._onDrop(event);
|
super._onDrop(event);
|
||||||
|
|
||||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||||
if (data.fromInternal) return;
|
if (data.fromInternal === this.document.id) return;
|
||||||
|
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
let item = await fromUuid(data.uuid);
|
let item = await fromUuid(data.uuid);
|
||||||
|
|
|
||||||
|
|
@ -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 => {
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,8 @@ export default class ClassSheet extends DHBaseItemSheet {
|
||||||
async _onDrop(event) {
|
async _onDrop(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const data = TextEditor.getDragEventData(event);
|
const data = TextEditor.getDragEventData(event);
|
||||||
const item = data.data ?? (await fromUuid(data.uuid));
|
const item = await fromUuid(data.uuid);
|
||||||
const itemType = data.data ? data.type : item.type;
|
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||||
const target = event.target.closest('fieldset.drop-section');
|
const target = event.target.closest('fieldset.drop-section');
|
||||||
if (itemType === 'subclass') {
|
if (itemType === 'subclass') {
|
||||||
if (item.system.linkedClass) {
|
if (item.system.linkedClass) {
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
@ -76,10 +78,10 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
||||||
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
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;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { DhCountdown } from '../../data/countdowns.mjs';
|
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||||
|
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -26,6 +27,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
|
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
|
||||||
editCountdownImage: CountdownEdit.#editCountdownImage,
|
editCountdownImage: CountdownEdit.#editCountdownImage,
|
||||||
editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
|
editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
|
||||||
|
randomiseCountdownStart: CountdownEdit.#randomiseCountdownStart,
|
||||||
removeCountdown: CountdownEdit.#removeCountdown
|
removeCountdown: CountdownEdit.#removeCountdown
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
form: { handler: this.updateData, submitOnChange: true }
|
||||||
|
|
@ -57,6 +59,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||||
: 'DAGGERHEART.UI.Countdowns.loop'
|
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||||
: null;
|
: null;
|
||||||
|
const randomizeValid = !new Roll(countdown.progress.startFormula ?? '').isDeterministic;
|
||||||
acc[key] = {
|
acc[key] = {
|
||||||
...countdown,
|
...countdown,
|
||||||
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
|
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
|
||||||
|
|
@ -67,6 +70,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
editing: this.editingCountdowns.has(key),
|
editing: this.editingCountdowns.has(key),
|
||||||
|
randomizeValid,
|
||||||
loopTooltip
|
loopTooltip
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -123,18 +127,26 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
// Sync current and max if max is changing and they were equal before
|
// Sync current and max if max is changing and they were equal before
|
||||||
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
|
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
|
||||||
const existing = this.data.countdowns[id];
|
const existing = this.data.countdowns[id];
|
||||||
const wasEqual = existing && existing.progress.current === existing.progress.max;
|
countdown.progress.current = this.getMatchingCurrentValue(
|
||||||
if (wasEqual && countdown.progress.max !== existing.progress.max) {
|
existing,
|
||||||
countdown.progress.current = countdown.progress.max;
|
countdown.progress.start,
|
||||||
} else {
|
countdown.progress.current
|
||||||
countdown.progress.current = Math.min(countdown.progress.current, countdown.progress.max);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hideNewCountdowns = hideNewCountdowns;
|
this.hideNewCountdowns = hideNewCountdowns;
|
||||||
this.updateSetting(settingsData);
|
this.updateSetting(settingsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMatchingCurrentValue(oldCount, newStart, newCurrent) {
|
||||||
|
const wasEqual = oldCount && oldCount.progress.current === oldCount.progress.start;
|
||||||
|
if (wasEqual && newStart !== oldCount.progress.start) {
|
||||||
|
return newStart;
|
||||||
|
} else {
|
||||||
|
return Math.min(newCurrent, newStart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async gmSetSetting(data) {
|
async gmSetSetting(data) {
|
||||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
||||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
|
@ -190,6 +202,21 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data });
|
this.updateSetting({ [`countdowns.${button.dataset.countdownId}`]: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #randomiseCountdownStart(_, button) {
|
||||||
|
const countdown = this.data.countdowns[button.dataset.countdownId];
|
||||||
|
const roll = await new Roll(countdown.progress.startFormula).roll();
|
||||||
|
const message = await roll.toMessage({ title: 'Countdown' });
|
||||||
|
|
||||||
|
await waitForDiceSoNice(message);
|
||||||
|
await this.updateSetting({
|
||||||
|
[`countdowns.${button.dataset.countdownId}.progress`]: {
|
||||||
|
start: roll.total,
|
||||||
|
current: this.getMatchingCurrentValue(countdown, roll.total, countdown.progress.current)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
static async #removeCountdown(event, button) {
|
static async #removeCountdown(event, button) {
|
||||||
const { countdownId } = button.dataset;
|
const { countdownId } = button.dataset;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
@ -13,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,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) => {
|
||||||
|
|
@ -123,13 +122,14 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
: 'DAGGERHEART.UI.Countdowns.loop'
|
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||||
: null;
|
: null;
|
||||||
const loopDisabled =
|
const loopDisabled =
|
||||||
!countdownEditable || (isLooping && (countdown.progress.current > 0 || countdown.progress.max === '0'));
|
!countdownEditable ||
|
||||||
|
(isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0'));
|
||||||
|
|
||||||
acc[key] = {
|
acc[key] = {
|
||||||
...countdown,
|
...countdown,
|
||||||
editable: countdownEditable,
|
editable: countdownEditable,
|
||||||
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0,
|
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0,
|
||||||
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.max > 0,
|
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.start > 0,
|
||||||
loopDisabled: isLooping ? loopDisabled : null,
|
loopDisabled: isLooping ? loopDisabled : null,
|
||||||
loopTooltip: isLooping && game.i18n.localize(loopTooltip)
|
loopTooltip: isLooping && game.i18n.localize(loopTooltip)
|
||||||
};
|
};
|
||||||
|
|
@ -182,16 +182,27 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
|
|
||||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const countdown = settings.countdowns[target.id];
|
const countdown = settings.countdowns[target.id];
|
||||||
|
|
||||||
|
let progressMax = countdown.progress.start;
|
||||||
|
let message = null;
|
||||||
|
if (countdown.progress.startFormula) {
|
||||||
|
const roll = await new Roll(countdown.progress.startFormula).evaluate();
|
||||||
|
progressMax = roll.total;
|
||||||
|
message = await roll.toMessage();
|
||||||
|
}
|
||||||
|
|
||||||
const newMax =
|
const newMax =
|
||||||
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
? countdown.progress.max + 1
|
? Number(progressMax) + 1
|
||||||
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
? Math.max(countdown.progress.max - 1, 0)
|
? Math.max(Number(progressMax) - 1, 0)
|
||||||
: countdown.progress.max;
|
: progressMax;
|
||||||
|
|
||||||
|
await waitForDiceSoNice(message);
|
||||||
await settings.updateSource({
|
await settings.updateSource({
|
||||||
[`countdowns.${target.id}.progress`]: {
|
[`countdowns.${target.id}.progress`]: {
|
||||||
current: newMax,
|
current: newMax,
|
||||||
max: newMax
|
start: newMax
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
|
|
@ -205,7 +216,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
const countdown = settings.countdowns[target.id];
|
const countdown = settings.countdowns[target.id];
|
||||||
const newCurrent = increase
|
const newCurrent = increase
|
||||||
? Math.min(countdown.progress.current + 1, countdown.progress.max)
|
? Math.min(countdown.progress.current + 1, countdown.progress.start)
|
||||||
: Math.max(countdown.progress.current - 1, 0);
|
: Math.max(countdown.progress.current - 1, 0);
|
||||||
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
||||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
|
|
@ -234,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,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]);
|
||||||
|
|
@ -260,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,
|
|
||||||
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',
|
||||||
|
|
@ -1519,6 +1489,10 @@ export const itemResourceTypes = {
|
||||||
diceValue: {
|
diceValue: {
|
||||||
id: 'diceValue',
|
id: 'diceValue',
|
||||||
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
|
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
|
||||||
|
},
|
||||||
|
die: {
|
||||||
|
id: 'die',
|
||||||
|
label: 'DAGGERHEART.CONFIG.ItemResourceType.die'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
||||||
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
||||||
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
||||||
|
baseAction: new fields.BooleanField({ initial: false }),
|
||||||
name: new fields.StringField({ initial: undefined }),
|
name: new fields.StringField({ initial: undefined }),
|
||||||
description: new fields.HTMLField(),
|
description: new fields.HTMLField(),
|
||||||
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
|
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
|
||||||
|
|
@ -32,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 => {
|
||||||
|
|
@ -94,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -158,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;
|
||||||
|
|
@ -192,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;
|
||||||
|
|
||||||
|
|
@ -207,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,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;
|
||||||
}
|
}
|
||||||
|
|
@ -321,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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@ export default class DhCountdownAction extends DHBaseAction {
|
||||||
...super.defaultValues,
|
...super.defaultValues,
|
||||||
countdown: {
|
countdown: {
|
||||||
name: this.parent.parent.name,
|
name: this.parent.parent.name,
|
||||||
img: this.img
|
img: this.img,
|
||||||
|
progress: {
|
||||||
|
startFormula: '1'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -21,10 +24,26 @@ export default class DhCountdownAction extends DHBaseAction {
|
||||||
{
|
{
|
||||||
...game.system.api.data.countdowns.DhCountdown.defaultCountdown(),
|
...game.system.api.data.countdowns.DhCountdown.defaultCountdown(),
|
||||||
name: parent.parent.name,
|
name: parent.parent.name,
|
||||||
img: parent.parent.img
|
img: parent.parent.img,
|
||||||
|
progress: {
|
||||||
|
startFormula: '1'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return updateSource;
|
return updateSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
static migrateData(source) {
|
||||||
|
for (const countdown of source.countdown) {
|
||||||
|
if (countdown.progress.max) {
|
||||||
|
countdown.progress.startFormula = countdown.progress.max;
|
||||||
|
countdown.progress.start = 1;
|
||||||
|
countdown.progress.max = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.migrateData(source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
integer: true,
|
integer: true,
|
||||||
label: 'DAGGERHEART.GENERAL.hordeHp'
|
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||||
}),
|
}),
|
||||||
|
criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }),
|
||||||
damageThresholds: new fields.SchemaField({
|
damageThresholds: new fields.SchemaField({
|
||||||
major: new fields.NumberField({
|
major: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -57,6 +59,9 @@ export default class DhpAdversary extends BaseDataActor {
|
||||||
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||||
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
|
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
|
||||||
}),
|
}),
|
||||||
|
rules: new fields.SchemaField({
|
||||||
|
...commonActorRules()
|
||||||
|
}),
|
||||||
attack: new ActionField({
|
attack: new ActionField({
|
||||||
initial: {
|
initial: {
|
||||||
name: 'Attack',
|
name: 'Attack',
|
||||||
|
|
@ -121,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,32 @@ 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() {
|
||||||
|
/* Clear all partyMembers from tagTeam setting.*/
|
||||||
|
/* Revisit this when tagTeam is improved for many parties */
|
||||||
|
if (this.parent.parties.size > 0) {
|
||||||
|
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
|
await tagTeam.updateSource({
|
||||||
|
initiator: this.parent.id === tagTeam.initiator ? null : tagTeam.initiator,
|
||||||
|
members: Object.keys(tagTeam.members).find(x => x === this.parent.id)
|
||||||
|
? { [`-=${this.parent.id}`]: null }
|
||||||
|
: {}
|
||||||
|
});
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _preUpdate(changes, options, userId) {
|
async _preUpdate(changes, options, userId) {
|
||||||
const allowed = await super._preUpdate(changes, options, userId);
|
const allowed = await super._preUpdate(changes, options, userId);
|
||||||
if (allowed === false) return;
|
if (allowed === false) return;
|
||||||
|
|
|
||||||
|
|
@ -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,40 +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 })
|
|
||||||
}),
|
|
||||||
disabledArmor: new fields.BooleanField({ intial: false })
|
|
||||||
}),
|
}),
|
||||||
attack: new fields.SchemaField({
|
attack: new fields.SchemaField({
|
||||||
damage: new fields.SchemaField({
|
damage: new fields.SchemaField({
|
||||||
|
|
@ -426,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 = [],
|
||||||
|
|
@ -434,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) {
|
||||||
|
|
@ -442,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) {
|
||||||
|
|
@ -669,6 +684,8 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
|
super._preDelete();
|
||||||
|
|
||||||
if (this.companion) {
|
if (this.companion) {
|
||||||
this.companion.updateLevel(1);
|
this.companion.updateLevel(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
@ -51,6 +51,13 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
rules: new fields.SchemaField({
|
||||||
|
conditionImmunities: new fields.SchemaField({
|
||||||
|
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: {
|
||||||
name: 'Attack',
|
name: 'Attack',
|
||||||
|
|
@ -101,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();
|
||||||
|
|
||||||
|
|
@ -36,6 +40,23 @@ export default class DhParty extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _preDelete() {
|
||||||
|
/* Clear all partyMembers from tagTeam setting.*/
|
||||||
|
/* Revisit this when tagTeam is improved for many parties */
|
||||||
|
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||||
|
await tagTeam.updateSource({
|
||||||
|
initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator,
|
||||||
|
members: Object.keys(tagTeam.members).reduce((acc, key) => {
|
||||||
|
if (this.partyMembers.find(x => x.id === key)) {
|
||||||
|
acc[`-=${key}`] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
});
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
||||||
|
}
|
||||||
|
|
||||||
_onDelete(options, userId) {
|
_onDelete(options, userId) {
|
||||||
super._onDelete(options, userId);
|
super._onDelete(options, userId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,10 +167,15 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
initial: 1,
|
initial: 1,
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
|
||||||
}),
|
}),
|
||||||
max: new FormulaField({
|
start: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
integer: true,
|
||||||
initial: 1,
|
initial: 1,
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.max.label',
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.start.label',
|
||||||
|
deterministic: false
|
||||||
|
}),
|
||||||
|
startFormula: new FormulaField({
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.startFormula.label',
|
||||||
deterministic: false
|
deterministic: false
|
||||||
}),
|
}),
|
||||||
looping: new fields.StringField({
|
looping: new fields.StringField({
|
||||||
|
|
@ -206,7 +211,7 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
ownership: ownership,
|
ownership: ownership,
|
||||||
progress: {
|
progress: {
|
||||||
current: 1,
|
current: 1,
|
||||||
max: 1
|
start: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -225,4 +230,15 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
static migrateData(source) {
|
||||||
|
if (source.progress.max) {
|
||||||
|
source.progress.start = Number(source.progress.max);
|
||||||
|
source.progress.max = null;
|
||||||
|
source.progress.startFormula = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.migrateData(source);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -40,18 +40,40 @@ export default class CountdownField extends fields.ArrayField {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = { countdowns: {} };
|
const data = { countdowns: {} };
|
||||||
|
const countdownMessages = [];
|
||||||
for (let countdown of config.countdowns) {
|
for (let countdown of config.countdowns) {
|
||||||
const { total: max } = await new Roll(countdown.progress.max).evaluate();
|
let startFormula = countdown.progress.startFormula ? countdown.progress.startFormula : null;
|
||||||
|
let countdownStart = startFormula ?? '1';
|
||||||
|
if (startFormula) {
|
||||||
|
const roll = await new Roll(startFormula).roll();
|
||||||
|
if (roll.dice.length > 0) {
|
||||||
|
countdownStart = roll.total;
|
||||||
|
const message = await roll.toMessage();
|
||||||
|
countdownMessages.push(message);
|
||||||
|
} else {
|
||||||
|
startFormula = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data.countdowns[foundry.utils.randomID()] = {
|
data.countdowns[foundry.utils.randomID()] = {
|
||||||
...countdown,
|
...countdown,
|
||||||
progress: {
|
progress: {
|
||||||
...countdown.progress,
|
...countdown.progress,
|
||||||
current: max,
|
current: countdownStart,
|
||||||
max: max
|
start: countdownStart,
|
||||||
|
startFormula
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
|
await Promise.all(
|
||||||
|
countdownMessages.map(message => {
|
||||||
|
return game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await emitAsGM(
|
await emitAsGM(
|
||||||
GMUpdateEvent.UpdateCountdowns,
|
GMUpdateEvent.UpdateCountdowns,
|
||||||
async () => {
|
async () => {
|
||||||
|
|
|
||||||
|
|
@ -47,15 +47,30 @@ export default class EffectsField extends fields.ArrayField {
|
||||||
static async applyEffects(targets) {
|
static async applyEffects(targets) {
|
||||||
if (!this.effects?.length || !targets?.length) return;
|
if (!this.effects?.length || !targets?.length) return;
|
||||||
|
|
||||||
|
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||||
let effects = this.effects;
|
let effects = this.effects;
|
||||||
const messageTargets = [];
|
const messageTargets = [];
|
||||||
targets.forEach(async baseToken => {
|
targets.forEach(async baseToken => {
|
||||||
if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
|
if (this.hasSave && baseToken.saved.success === true) effects = this.effects.filter(e => e.onSave === true);
|
||||||
if (!effects.length) return;
|
if (!effects.length) return;
|
||||||
|
|
||||||
const token = canvas.tokens.get(baseToken.id);
|
const token =
|
||||||
|
canvas.tokens.get(baseToken.id) ?? foundry.utils.fromUuidSync(baseToken.actorId).prototypeToken;
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
messageTargets.push(token.document);
|
|
||||||
|
const messageToken = token.document ?? token;
|
||||||
|
const conditionImmunities = messageToken.actor.system.rules.conditionImmunities ?? {};
|
||||||
|
messageTargets.push({
|
||||||
|
token: messageToken,
|
||||||
|
conditionImmunities: Object.values(conditionImmunities).some(x => x)
|
||||||
|
? game.i18n.format('DAGGERHEART.UI.Chat.effectSummary.immunityTo', {
|
||||||
|
immunities: Object.keys(conditionImmunities)
|
||||||
|
.filter(x => conditionImmunities[x])
|
||||||
|
.map(x => game.i18n.localize(conditions[x].name))
|
||||||
|
.join(', ')
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
|
||||||
effects.forEach(async e => {
|
effects.forEach(async e => {
|
||||||
const effect = this.item.effects.get(e._id);
|
const effect = this.item.effects.get(e._id);
|
||||||
|
|
|
||||||
|
|
@ -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,18 +262,27 @@ 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: { name: this.name, img: this.img, tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'] },
|
action: {
|
||||||
|
name: this.name,
|
||||||
|
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||||
|
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10']
|
||||||
|
},
|
||||||
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'
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ export default class DHWeapon extends AttachableItem {
|
||||||
name: 'Attack',
|
name: 'Attack',
|
||||||
img: 'icons/skills/melee/blood-slash-foam-red.webp',
|
img: 'icons/skills/melee/blood-slash-foam-red.webp',
|
||||||
_id: foundry.utils.randomID(),
|
_id: foundry.utils.randomID(),
|
||||||
|
baseAction: true,
|
||||||
|
chatDisplay: false,
|
||||||
systemPath: 'attack',
|
systemPath: 'attack',
|
||||||
type: 'attack',
|
type: 'attack',
|
||||||
range: 'melee',
|
range: 'melee',
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
||||||
texture: new StringField({ initial: 'astralsea', required: true, blank: false }),
|
texture: new StringField({ initial: 'astralsea', required: true, blank: false }),
|
||||||
colorset: new StringField({ initial: 'inspired', required: true, blank: false }),
|
colorset: new StringField({ initial: 'inspired', required: true, blank: false }),
|
||||||
material: new StringField({ initial: 'metal', required: true, blank: false }),
|
material: new StringField({ initial: 'metal', required: true, blank: false }),
|
||||||
system: new StringField({ initial: 'standard', required: true, blank: false })
|
system: new StringField({ initial: 'standard', required: true, blank: false }),
|
||||||
|
font: new StringField({ initial: 'auto', required: true, blank: false })
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ export default class D20Roll extends DHRoll {
|
||||||
DISADVANTAGE: -1
|
DISADVANTAGE: -1
|
||||||
};
|
};
|
||||||
|
|
||||||
static CRITICAL_TRESHOLD = 20;
|
|
||||||
|
|
||||||
static DefaultDialog = D20RollDialog;
|
static DefaultDialog = D20RollDialog;
|
||||||
|
|
||||||
get title() {
|
get title() {
|
||||||
|
|
@ -37,7 +35,7 @@ export default class D20Roll extends DHRoll {
|
||||||
|
|
||||||
get isCritical() {
|
get isCritical() {
|
||||||
if (!this.d20._evaluated) return;
|
if (!this.d20._evaluated) return;
|
||||||
return this.d20.total >= this.constructor.CRITICAL_TRESHOLD;
|
return this.d20.total >= this.data.system.criticalThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasAdvantage() {
|
get hasAdvantage() {
|
||||||
|
|
@ -100,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 {
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -57,6 +58,27 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const immuneStatuses =
|
||||||
|
data.statuses?.filter(
|
||||||
|
status =>
|
||||||
|
this.parent.system.rules?.conditionImmunities &&
|
||||||
|
this.parent.system.rules.conditionImmunities[status]
|
||||||
|
) ?? [];
|
||||||
|
if (immuneStatuses.length > 0) {
|
||||||
|
update.statuses = data.statuses.filter(x => !immuneStatuses.includes(x));
|
||||||
|
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||||
|
const scrollingTexts = immuneStatuses.map(status => ({
|
||||||
|
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
|
||||||
|
status: game.i18n.localize(conditions[status].name)
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
if (update.statuses.length > 0) {
|
||||||
|
setTimeout(() => scrollingTexts, 500);
|
||||||
|
} else {
|
||||||
|
this.parent.queueScrollText(scrollingTexts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(update).length > 0) {
|
if (Object.keys(update).length > 0) {
|
||||||
await this.updateSource(update);
|
await this.updateSource(update);
|
||||||
}
|
}
|
||||||
|
|
@ -64,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 */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
@ -76,7 +112,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||||
change.value = change.value.replaceAll(/origin\.@/gi, '@');
|
change.value = change.value.replaceAll(/origin\.@/gi, '@');
|
||||||
try {
|
try {
|
||||||
const effect = foundry.utils.fromUuidSync(change.effect.origin);
|
const effect = foundry.utils.fromUuidSync(change.effect.origin);
|
||||||
const doc = effect.parent?.parent;
|
const doc =
|
||||||
|
effect.parent?.parent instanceof game.system.api.documents.DhpActor
|
||||||
|
? effect.parent
|
||||||
|
: effect.parent.parent;
|
||||||
if (doc) parseModel = doc;
|
if (doc) parseModel = doc;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
@ -155,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) {
|
||||||
|
|
@ -204,7 +216,7 @@ export default class DhpActor extends Actor {
|
||||||
|
|
||||||
for (let domainCard of domainCards) {
|
for (let domainCard of domainCards) {
|
||||||
const itemCard = this.items.find(x => x.uuid === domainCard);
|
const itemCard = this.items.find(x => x.uuid === domainCard);
|
||||||
itemCard.delete();
|
itemCard?.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,6 +349,8 @@ export default class DhpActor extends Actor {
|
||||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||||
{
|
{
|
||||||
...multiclassData,
|
...multiclassData,
|
||||||
|
uuid: multiclassItem.uuid,
|
||||||
|
_stats: multiclassItem._stats,
|
||||||
system: {
|
system: {
|
||||||
...multiclassData.system,
|
...multiclassData.system,
|
||||||
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
|
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
|
||||||
|
|
@ -349,6 +363,8 @@ export default class DhpActor extends Actor {
|
||||||
await this.createEmbeddedDocuments('Item', [
|
await this.createEmbeddedDocuments('Item', [
|
||||||
{
|
{
|
||||||
...subclassData,
|
...subclassData,
|
||||||
|
uuid: subclassItem.uuid,
|
||||||
|
_stats: subclassItem._stats,
|
||||||
system: {
|
system: {
|
||||||
...subclassData.system,
|
...subclassData.system,
|
||||||
isMulticlass: true
|
isMulticlass: true
|
||||||
|
|
@ -363,12 +379,15 @@ export default class DhpActor extends Actor {
|
||||||
|
|
||||||
for (var domainCard of domainCards) {
|
for (var domainCard of domainCards) {
|
||||||
if (levelupAuto) {
|
if (levelupAuto) {
|
||||||
const itemData = (await foundry.utils.fromUuid(domainCard.data[0])).toObject();
|
const cardItem = await foundry.utils.fromUuid(domainCard.data[0]);
|
||||||
|
const cardData = cardItem.toObject();
|
||||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||||
{
|
{
|
||||||
...itemData,
|
...cardData,
|
||||||
|
uuid: cardItem.uuid,
|
||||||
|
_stats: cardItem._stats,
|
||||||
system: {
|
system: {
|
||||||
...itemData.system,
|
...cardData.system,
|
||||||
inVault: true
|
inVault: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -382,12 +401,15 @@ export default class DhpActor extends Actor {
|
||||||
const achievementDomainCards = [];
|
const achievementDomainCards = [];
|
||||||
if (levelupAuto) {
|
if (levelupAuto) {
|
||||||
for (var card of Object.values(level.achievements.domainCards)) {
|
for (var card of Object.values(level.achievements.domainCards)) {
|
||||||
const itemData = (await foundry.utils.fromUuid(card.uuid)).toObject();
|
const cardItem = await foundry.utils.fromUuid(card.uuid);
|
||||||
|
const cardData = cardItem.toObject();
|
||||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||||
{
|
{
|
||||||
...itemData,
|
...cardData,
|
||||||
|
uuid: cardItem.uuid,
|
||||||
|
_stats: cardItem._stats,
|
||||||
system: {
|
system: {
|
||||||
...itemData.system,
|
...cardData.system,
|
||||||
inVault: true
|
inVault: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -467,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);
|
||||||
}
|
}
|
||||||
|
|
@ -516,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;
|
||||||
|
|
@ -604,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(
|
||||||
|
|
@ -669,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;
|
||||||
|
|
||||||
|
|
@ -751,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -834,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];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -437,3 +438,46 @@ export function shuffleArray(array) {
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function itemIsIdentical(a, b) {
|
||||||
|
const compendiumSource = a._stats.compendiumSource === b._stats.compendiumSource;
|
||||||
|
const name = a.name === b.name;
|
||||||
|
const description = a.system.description === b.system.description;
|
||||||
|
|
||||||
|
return compendiumSource && name & description;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitForDiceSoNice(message) {
|
||||||
|
if (message && game.modules.get('dice-so-nice')?.active) {
|
||||||
|
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 || '';
|
||||||
|
}
|
||||||
|
|
|
||||||
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