mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 19:51:08 +01:00
Compare commits
153 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 | ||
|
|
207220ff7b | ||
|
|
0233979a9f | ||
|
|
844c683c7d | ||
|
|
72944698c5 | ||
|
|
28401989d0 | ||
|
|
d1cbaa7809 | ||
|
|
a02015df57 | ||
|
|
8e785a5890 | ||
|
|
336995b748 | ||
|
|
a146132171 | ||
|
|
5c52a33496 | ||
|
|
b9d67e44da | ||
|
|
fe8e98ef35 | ||
|
|
c3660ffa34 | ||
|
|
97f408c185 | ||
|
|
023e17d47d | ||
|
|
481ce46edf | ||
|
|
7df43d71e0 | ||
|
|
fcb9c032ef | ||
|
|
91b1b92d19 | ||
|
|
369559f935 | ||
|
|
97ddf651a0 | ||
|
|
bdbca0d35a | ||
|
|
ef0290ae8a | ||
|
|
f730bb762c | ||
|
|
fbf1ee2046 | ||
|
|
249f27ed5b | ||
|
|
63e66bd0d3 | ||
|
|
977715d22a |
1429 changed files with 14935 additions and 25322 deletions
296
daggerheart.mjs
296
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';
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
|
||||||
CONFIG.DH = SYSTEM;
|
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', () => {
|
||||||
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 effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
|
||||||
if (!effectsAutomation.rangeDependent) return;
|
|
||||||
|
|
||||||
const rangeDependantEffects = movedToken.actor.effects.filter(effect => effect.system.rangeDependence?.enabled);
|
|
||||||
|
|
||||||
const updateEffects = async (disposition, token, effects, effectUpdates) => {
|
|
||||||
const rangeMeasurement = game.settings.get(
|
const rangeMeasurement = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||||
).rangeMeasurement;
|
).rangeMeasurement;
|
||||||
|
|
||||||
for (let effect of effects.filter(x => x.system.rangeDependence?.enabled)) {
|
for (let effect of token.actor?.allApplicableEffects() ?? []) {
|
||||||
|
if (!effect.system.rangeDependence?.enabled) continue;
|
||||||
const { target, range, type } = effect.system.rangeDependence;
|
const { target, range, type } = effect.system.rangeDependence;
|
||||||
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const distanceBetween = canvas.grid.measurePath([
|
// If there are no targets, assume false. Otherwise, start with the effect enabled.
|
||||||
{ ...movedToken.toObject(), x: data.destination.x, y: data.destination.y },
|
let enabledEffect = game.user.targets.size !== 0;
|
||||||
token
|
// Expect all targets to meet the rangeDependence requirements
|
||||||
]).distance;
|
for (let userTarget of game.user.targets) {
|
||||||
const distance = rangeMeasurement[range];
|
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 reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
||||||
const newDisabled = reverse ? distanceBetween <= distance : distanceBetween > distance;
|
const inRange =
|
||||||
const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
|
required === 5
|
||||||
effectUpdates[effect.uuid] = {
|
? userTarget.isAdjacentWith(token.object)
|
||||||
disabled: oldDisabled || newDisabled,
|
: userTarget.distanceTo(token.object) <= required;
|
||||||
value: effect
|
if (reverse ? inRange : !inRange) {
|
||||||
};
|
enabledEffect = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await effect.update({ disabled: !enabledEffect });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const effectUpdates = {};
|
const updateAllRangeDependentEffects = async () => {
|
||||||
for (let token of game.scenes.find(x => x.active).tokens) {
|
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||||
if (token.id !== movedToken.id) {
|
if (!effectsAutomation.rangeDependent) return;
|
||||||
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
|
const tokens = canvas.scene.tokens;
|
||||||
|
if (game.user.character) {
|
||||||
|
// The character updates their character's token. There can be only one token.
|
||||||
|
const characterToken = tokens.find(x => x.actor === game.user.character);
|
||||||
|
updateActorsRangeDependentEffects(characterToken);
|
||||||
|
} else if (game.user.isActiveGM) {
|
||||||
|
// The GM is responsible for all other tokens.
|
||||||
|
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
|
||||||
|
for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
|
||||||
|
updateActorsRangeDependentEffects(token);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
198
lang/en.json
198
lang/en.json
|
|
@ -36,6 +36,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
"DAGGERHEART": {
|
"DAGGERHEART": {
|
||||||
|
"CharacterSheet": "Character Sheet",
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"TYPES": {
|
"TYPES": {
|
||||||
"attack": {
|
"attack": {
|
||||||
|
|
@ -46,6 +47,10 @@
|
||||||
"name": "Beastform",
|
"name": "Beastform",
|
||||||
"tooltip": "Shapeshift the user into another form."
|
"tooltip": "Shapeshift the user into another form."
|
||||||
},
|
},
|
||||||
|
"countdown": {
|
||||||
|
"name": "Countdown",
|
||||||
|
"tooltip": "Start a countdown"
|
||||||
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
"name": "Damage",
|
"name": "Damage",
|
||||||
"tooltip": "Direct damage without a roll."
|
"tooltip": "Direct damage without a roll."
|
||||||
|
|
@ -73,6 +78,10 @@
|
||||||
"exactHint": "The Character's Tier is used if empty",
|
"exactHint": "The Character's Tier is used if empty",
|
||||||
"label": "Beastform"
|
"label": "Beastform"
|
||||||
},
|
},
|
||||||
|
"countdown": {
|
||||||
|
"defaultOwnership": "Default Ownership",
|
||||||
|
"startCountdown": "Start Countdown"
|
||||||
|
},
|
||||||
"damage": {
|
"damage": {
|
||||||
"multiplier": "Multiplier",
|
"multiplier": "Multiplier",
|
||||||
"flatMultiplier": "Flat Multiplier"
|
"flatMultiplier": "Flat Multiplier"
|
||||||
|
|
@ -95,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.",
|
||||||
|
|
@ -122,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": {
|
||||||
|
|
@ -215,8 +226,10 @@
|
||||||
"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"
|
||||||
},
|
},
|
||||||
"Companion": {
|
"Companion": {
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
|
|
@ -314,26 +327,34 @@
|
||||||
"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"
|
||||||
"cancelBeastform": "Cancel Beastform"
|
|
||||||
},
|
},
|
||||||
"Countdown": {
|
"Countdown": {
|
||||||
"addCountdown": "Add Countdown",
|
"addCountdown": "Add Countdown",
|
||||||
|
"loopingTypes": {
|
||||||
|
"noLooping": "No Looping",
|
||||||
|
"looping": "Looping",
|
||||||
|
"increasing": "Increasing",
|
||||||
|
"decreasing": "Decreasing"
|
||||||
|
},
|
||||||
"FIELDS": {
|
"FIELDS": {
|
||||||
"countdowns": {
|
"countdowns": {
|
||||||
"element": {
|
"element": {
|
||||||
"name": { "label": "Name" },
|
"name": { "label": "Name" },
|
||||||
"progress": {
|
"progress": {
|
||||||
"current": { "label": "Current" },
|
"current": { "label": "Current" },
|
||||||
"max": { "label": "Max" },
|
"looping": { "label": "Looping" },
|
||||||
|
"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" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": { "label": "Countdown Type" }
|
"type": { "label": "Progression Type" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -355,17 +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",
|
||||||
"type": "Type",
|
"progressionType": "Progression",
|
||||||
|
"decreasing": "Decreasing",
|
||||||
|
"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}",
|
||||||
|
|
@ -379,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",
|
||||||
|
|
@ -447,6 +473,9 @@
|
||||||
"title": "Select Image",
|
"title": "Select Image",
|
||||||
"selectImage": "Select Image"
|
"selectImage": "Select Image"
|
||||||
},
|
},
|
||||||
|
"ItemTransfer": {
|
||||||
|
"transfer": "Transfer"
|
||||||
|
},
|
||||||
"Levelup": {
|
"Levelup": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"creatureComfort": {
|
"creatureComfort": {
|
||||||
|
|
@ -479,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.",
|
||||||
|
|
@ -558,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": {
|
||||||
|
|
@ -584,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": {
|
||||||
|
|
@ -593,11 +627,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"ActionType": {
|
|
||||||
"passive": "Passive",
|
|
||||||
"action": "Action",
|
|
||||||
"reaction": "Reaction"
|
|
||||||
},
|
|
||||||
"AdversaryTrait": {
|
"AdversaryTrait": {
|
||||||
"relentless": {
|
"relentless": {
|
||||||
"name": "Relentless",
|
"name": "Relentless",
|
||||||
|
|
@ -657,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",
|
||||||
|
|
@ -880,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"
|
||||||
|
|
@ -915,9 +976,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CountdownType": {
|
"CountdownType": {
|
||||||
"spotlight": "Spotlight",
|
"actionRoll": "Action Roll",
|
||||||
|
"characterAttack": "Character Attack",
|
||||||
|
"characterSpotlight": "Character Spotlight",
|
||||||
"custom": "Custom",
|
"custom": "Custom",
|
||||||
"characterAttack": "Character Attack"
|
"fear": "Fear",
|
||||||
|
"spotlight": "Spotlight"
|
||||||
},
|
},
|
||||||
"DamageType": {
|
"DamageType": {
|
||||||
"physical": {
|
"physical": {
|
||||||
|
|
@ -971,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",
|
||||||
|
|
@ -1011,7 +1081,8 @@
|
||||||
},
|
},
|
||||||
"ItemResourceType": {
|
"ItemResourceType": {
|
||||||
"simple": "Simple",
|
"simple": "Simple",
|
||||||
"diceValue": "Dice Value"
|
"diceValue": "Dice Value",
|
||||||
|
"die": "Die"
|
||||||
},
|
},
|
||||||
"Range": {
|
"Range": {
|
||||||
"self": {
|
"self": {
|
||||||
|
|
@ -1083,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",
|
||||||
|
|
@ -1192,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",
|
||||||
|
|
@ -1723,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"
|
||||||
|
|
@ -1738,6 +1819,7 @@
|
||||||
"plural": "Costs"
|
"plural": "Costs"
|
||||||
},
|
},
|
||||||
"Damage": {
|
"Damage": {
|
||||||
|
"massive": "Massive",
|
||||||
"severe": "Severe",
|
"severe": "Severe",
|
||||||
"major": "Major",
|
"major": "Major",
|
||||||
"minor": "Minor",
|
"minor": "Minor",
|
||||||
|
|
@ -2003,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",
|
||||||
|
|
@ -2029,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",
|
||||||
|
|
@ -2043,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",
|
||||||
|
|
@ -2064,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",
|
||||||
|
|
@ -2078,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",
|
||||||
|
|
@ -2098,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",
|
||||||
|
|
@ -2154,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" }
|
||||||
},
|
},
|
||||||
|
|
@ -2177,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",
|
||||||
|
|
@ -2286,6 +2380,10 @@
|
||||||
"gm": { "label": "GM" },
|
"gm": { "label": "GM" },
|
||||||
"players": { "label": "Players" }
|
"players": { "label": "Players" }
|
||||||
},
|
},
|
||||||
|
"countdownAutomation": {
|
||||||
|
"label": "Countdown Automation",
|
||||||
|
"hint": "Automatically progress countdowns based on their progression settings"
|
||||||
|
},
|
||||||
"levelupAuto": {
|
"levelupAuto": {
|
||||||
"label": "Levelup Automation",
|
"label": "Levelup Automation",
|
||||||
"hint": "When you've made your choices and finish levelup, the numerical changes are automatically applied to your character."
|
"hint": "When you've made your choices and finish levelup, the numerical changes are automatically applied to your character."
|
||||||
|
|
@ -2349,7 +2447,7 @@
|
||||||
"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 Item Feature",
|
"newFeature": "New Item Feature",
|
||||||
"downtimeMoves": "Downtime Moves",
|
"downtimeMoves": "Downtime Moves",
|
||||||
"itemFeatures": "Item Features",
|
"itemFeatures": "Item Features",
|
||||||
|
|
@ -2366,11 +2464,13 @@
|
||||||
"maxDomains": { "label": "Max Class Domains", "hint": "Max domains you can set on a class" }
|
"maxDomains": { "label": "Max Class Domains", "hint": "Max domains you can set on a class" }
|
||||||
},
|
},
|
||||||
"currency": {
|
"currency": {
|
||||||
"enabled": "Enable Overrides",
|
|
||||||
"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"
|
||||||
},
|
},
|
||||||
|
|
@ -2422,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": {
|
||||||
|
|
@ -2431,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": {
|
||||||
|
|
@ -2444,6 +2550,10 @@
|
||||||
"actionTokens": {
|
"actionTokens": {
|
||||||
"enabled": { "label": "Enabled" },
|
"enabled": { "label": "Enabled" },
|
||||||
"tokens": { "label": "Tokens" }
|
"tokens": { "label": "Tokens" }
|
||||||
|
},
|
||||||
|
"massiveDamage": {
|
||||||
|
"title": "Massive Damage",
|
||||||
|
"enabled": { "label": "Enabled" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -2503,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": {
|
||||||
|
|
@ -2515,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}",
|
||||||
|
|
@ -2546,7 +2658,14 @@
|
||||||
"Countdowns": {
|
"Countdowns": {
|
||||||
"title": "Countdowns",
|
"title": "Countdowns",
|
||||||
"toggleIconMode": "Toggle Icon Only",
|
"toggleIconMode": "Toggle Icon Only",
|
||||||
"noPlayerAccess": "This countdown isn't visible to any players"
|
"noPlayerAccess": "This countdown isn't visible to any players",
|
||||||
|
"loop": "Looping",
|
||||||
|
"decreasingLoop": "Decreasing Looping",
|
||||||
|
"increasingLoop": "Increasing Looping"
|
||||||
|
},
|
||||||
|
"EffectsDisplay": {
|
||||||
|
"removeThing": "[Right Click] Remove {thing}",
|
||||||
|
"appliedBy": "Applied By: {by}"
|
||||||
},
|
},
|
||||||
"ItemBrowser": {
|
"ItemBrowser": {
|
||||||
"title": "Daggerheart Compendium Browser",
|
"title": "Daggerheart Compendium Browser",
|
||||||
|
|
@ -2570,6 +2689,7 @@
|
||||||
"evasionMin": "Evasion (Min)",
|
"evasionMin": "Evasion (Min)",
|
||||||
"evasionMax": "Evasion (Max)",
|
"evasionMax": "Evasion (Max)",
|
||||||
"subtype": "Subtype",
|
"subtype": "Subtype",
|
||||||
|
"missing": "<i>Missing</i>",
|
||||||
"folders": {
|
"folders": {
|
||||||
"characters": "Characters",
|
"characters": "Characters",
|
||||||
"adversaries": "Adversaries",
|
"adversaries": "Adversaries",
|
||||||
|
|
@ -2623,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 drag only characters to a 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",
|
||||||
|
|
@ -2660,9 +2780,18 @@
|
||||||
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
|
||||||
"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.",
|
||||||
|
"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",
|
||||||
|
|
@ -2696,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}`),
|
||||||
|
|
@ -178,11 +175,23 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
||||||
}
|
}
|
||||||
|
|
||||||
static async takeDowntime() {
|
static async takeDowntime() {
|
||||||
const moves = Object.values(this.moveData).flatMap(category => {
|
const moves = Object.keys(this.moveData).flatMap(categoryKey => {
|
||||||
return Object.values(category.moves)
|
const category = this.moveData[categoryKey];
|
||||||
.filter(x => x.selected)
|
return Object.keys(category.moves)
|
||||||
.flatMap(move => [...Array(move.selected).keys()].map(_ => move));
|
.filter(x => category.moves[x].selected)
|
||||||
|
.flatMap(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(_ => ({
|
||||||
|
...move,
|
||||||
|
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 = {
|
||||||
|
|
@ -202,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: {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default class ImageSelectDialog extends HandlebarsApplicationMixin(Applic
|
||||||
tag: 'form',
|
tag: 'form',
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'image-select'],
|
classes: ['daggerheart', 'dialog', 'dh-style', 'image-select'],
|
||||||
position: {
|
position: {
|
||||||
width: 600,
|
width: 612,
|
||||||
height: 'auto'
|
height: 'auto'
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
|
|
@ -30,7 +30,10 @@ export default class ImageSelectDialog extends HandlebarsApplicationMixin(Applic
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
main: { template: 'systems/daggerheart/templates/dialogs/image-select/main.hbs' },
|
main: {
|
||||||
|
template: 'systems/daggerheart/templates/dialogs/image-select/main.hbs',
|
||||||
|
scrollable: ['.images-container']
|
||||||
|
},
|
||||||
footer: { template: 'systems/daggerheart/templates/dialogs/image-select/footer.hbs' }
|
footer: { template: 'systems/daggerheart/templates/dialogs/image-select/footer.hbs' }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
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
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const member of Object.values(context.members)) {
|
||||||
|
if (!member.roll) continue;
|
||||||
if (context.usesDamage) {
|
if (context.usesDamage) {
|
||||||
if (!acc.damageValues) acc.damageValues = {};
|
if (!context.selectedData.damageValues) context.selectedData.damageValues = {};
|
||||||
for (let damage of member.damageValues) {
|
for (let damage of member.damageValues) {
|
||||||
if (acc.damageValues[damage.key]) {
|
const damageTotal = member.roll.system.isCritical
|
||||||
acc.damageValues[damage.key].total += damage.total;
|
? 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 {
|
} else {
|
||||||
acc.damageValues[damage.key] = foundry.utils.deepClone(damage);
|
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) {
|
||||||
} else {
|
const criticalDamage = await getCritDamageBonus(part.formula);
|
||||||
systemData.damage[key] = damage;
|
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.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
|
||||||
|
|
||||||
|
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 {
|
||||||
|
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
if (experience.name) {
|
||||||
acc.push({ id: id, label: 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,240 +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.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);
|
||||||
|
|
@ -254,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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -112,7 +115,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
||||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||||
).showGenericStatusEffects;
|
).showGenericStatusEffects;
|
||||||
if (!useGeneric) {
|
if (!useGeneric) {
|
||||||
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions()).map(status => ({
|
||||||
value: status.id,
|
value: status.id,
|
||||||
label: game.i18n.localize(status.name)
|
label: game.i18n.localize(status.name)
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
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: DhPrototypeTokenConfig.TURN_MARKER_MODES,
|
|
||||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process form submission for the sheet
|
||||||
|
* @this {PrototypeTokenConfig}
|
||||||
|
* @type {ApplicationFormSubmission}
|
||||||
|
*/
|
||||||
|
static async #onSubmit(event, form, formData) {
|
||||||
|
const submitData = this._processFormData(event, form, formData);
|
||||||
|
submitData.detectionModes ??= []; // Clear detection modes array
|
||||||
|
this._processChanges(submitData);
|
||||||
|
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 = [
|
||||||
|
...context.statuses,
|
||||||
|
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
||||||
value: status.id,
|
value: status.id,
|
||||||
label: game.i18n.localize(status.name)
|
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) {
|
||||||
|
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 });
|
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,7 +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,
|
||||||
useDowntime: this.useDowntime
|
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||||
|
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||||
|
useDowntime: this.useDowntime,
|
||||||
|
viewParty: CharacterSheet.#viewParty,
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true,
|
resizable: true,
|
||||||
|
|
@ -45,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
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -138,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 */
|
||||||
|
|
@ -209,24 +211,7 @@ 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 = {
|
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||||
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')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency;
|
|
||||||
if (homebrewCurrency.enabled) {
|
|
||||||
context.inventory.currency = homebrewCurrency;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.inventory.length === 0) {
|
|
||||||
context.inventory = Array(1).fill(Array(5).fill([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -334,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',
|
||||||
|
|
@ -605,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;
|
||||||
|
|
@ -699,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
|
||||||
|
|
@ -848,6 +863,71 @@ 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]))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static async #cancelBeastform(_, target) {
|
||||||
|
const item = await getDocFromElement(target);
|
||||||
|
if (!item) return;
|
||||||
|
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}
|
||||||
|
|
@ -858,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') {
|
||||||
|
|
@ -895,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ export default class Party extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['party'],
|
classes: ['party'],
|
||||||
position: {
|
position: {
|
||||||
width: 550
|
width: 550,
|
||||||
|
height: 900
|
||||||
},
|
},
|
||||||
window: {
|
window: {
|
||||||
resizable: true
|
resizable: true
|
||||||
|
|
@ -39,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 */
|
||||||
|
|
@ -79,6 +80,9 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
||||||
|
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||||
|
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
await super._onRender(context, options);
|
await super._onRender(context, options);
|
||||||
this._createFilterMenus();
|
this._createFilterMenus();
|
||||||
|
|
@ -89,31 +93,6 @@ export default class Party extends DHBaseActorSheet {
|
||||||
/* Prepare Context */
|
/* Prepare Context */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
const context = await super._prepareContext(_options);
|
|
||||||
|
|
||||||
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')
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency;
|
|
||||||
if (homebrewCurrency.enabled) {
|
|
||||||
context.inventory.currency = homebrewCurrency;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.inventory.length === 0) {
|
|
||||||
context.inventory = Array(1).fill(Array(5).fill([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
|
|
@ -277,13 +256,17 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #tagTeamRoll() {
|
static async #tagTeamRoll() {
|
||||||
new game.system.api.applications.dialogs.TagTeamDialog(this.document.system.partyMembers).render({
|
new game.system.api.applications.dialogs.TagTeamDialog(
|
||||||
|
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
).render({
|
||||||
force: true
|
force: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(params) {
|
static async #groupRoll(_params) {
|
||||||
new GroupRollDialog(this.document.system.partyMembers).render({ force: true });
|
new GroupRollDialog(
|
||||||
|
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
||||||
|
).render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -437,36 +420,20 @@ 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 item = await foundry.utils.fromUuid(data.uuid);
|
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
||||||
|
|
||||||
if (item instanceof DhpActor) {
|
|
||||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||||
if (currentMembers.includes(data.uuid)) {
|
if (currentMembers.includes(data.uuid)) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.document.update({ 'system.partyMembers': [...currentMembers, item.uuid] });
|
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
|
||||||
} else if (item instanceof DHItem) {
|
|
||||||
this.document.createEmbeddedDocuments('Item', [item.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -420,19 +494,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
];
|
];
|
||||||
|
|
||||||
if (usable) {
|
if (usable) {
|
||||||
options.unshift({
|
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.cancelBeastform',
|
|
||||||
icon: 'fa-solid fa-ban',
|
|
||||||
condition: target => {
|
|
||||||
const doc = getDocFromElementSync(target);
|
|
||||||
return doc && doc.system?.actions?.some(a => a.type === 'beastform');
|
|
||||||
},
|
|
||||||
callback: async target =>
|
|
||||||
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(
|
|
||||||
await getDocFromElement(target)
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.GENERAL.damage',
|
name: 'DAGGERHEART.GENERAL.damage',
|
||||||
icon: 'fa-solid fa-explosion',
|
icon: 'fa-solid fa-explosion',
|
||||||
|
|
@ -675,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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
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,19 +199,33 @@ 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 }))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a resource to the item.
|
* Add a resource to the item.
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -132,12 +133,24 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
}
|
}
|
||||||
|
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
const { moveIndex, actionIndex } = 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(
|
||||||
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
|
{
|
||||||
|
...actionType,
|
||||||
|
_id: foundry.utils.randomID(),
|
||||||
|
name: game.i18n.localize(actionType.name),
|
||||||
|
originItem: {
|
||||||
|
type: CONFIG.DH.ITEM.originItemType.restMove,
|
||||||
|
itemPath: movePath,
|
||||||
|
actionIndex: actionIndex
|
||||||
|
},
|
||||||
|
targetUuid: targetUuid
|
||||||
|
},
|
||||||
{ parent: parent.system }
|
{ parent: parent.system }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -192,9 +205,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
|
|
||||||
async groupRollButton(event, message) {
|
async groupRollButton(event, message) {
|
||||||
const path = event.currentTarget.dataset.path;
|
const path = event.currentTarget.dataset.path;
|
||||||
|
const isLeader = path === 'leader';
|
||||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
||||||
const actor = game.actors.get(actorData._id);
|
const actor = game.actors.get(actorData._id);
|
||||||
|
|
||||||
|
if (!actor) {
|
||||||
|
return ui.notifications.error(
|
||||||
|
game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', {
|
||||||
|
documentType: game.i18n.localize('TYPES.Actor.character')
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
||||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +236,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
skips: {
|
skips: {
|
||||||
createMessage: true,
|
createMessage: true,
|
||||||
resources: true
|
resources: !isLeader,
|
||||||
|
updateCountdowns: !isLeader
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const result = await actor.diceRoll({
|
const result = await actor.diceRoll({
|
||||||
|
|
@ -225,6 +248,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
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);
|
||||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
||||||
|
|
@ -270,7 +295,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
},
|
},
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
skips: {
|
skips: {
|
||||||
createMessage: true
|
createMessage: true,
|
||||||
|
updateCountdowns: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const result = await actor.diceRoll({
|
const result = await actor.diceRoll({
|
||||||
|
|
|
||||||
|
|
@ -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,7 +165,14 @@ 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.countdownTypes.spotlight.id);
|
if (combatant.actor?.type === 'character') {
|
||||||
|
await updateCountdowns(
|
||||||
|
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;
|
||||||
if (autoPoints) {
|
if (autoPoints) {
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
|
|
@ -44,18 +46,32 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
||||||
context.defaultOwnership = this.data.defaultOwnership;
|
context.defaultOwnership = this.data.defaultOwnership;
|
||||||
context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes;
|
context.countdownBaseTypes = CONFIG.DH.GENERAL.countdownBaseTypes;
|
||||||
context.countdownTypes = CONFIG.DH.GENERAL.countdownTypes;
|
context.countdownProgressionTypes = CONFIG.DH.GENERAL.countdownProgressionTypes;
|
||||||
|
context.countdownLoopingTypes = CONFIG.DH.GENERAL.countdownLoopingTypes;
|
||||||
context.hideNewCountdowns = this.hideNewCountdowns;
|
context.hideNewCountdowns = this.hideNewCountdowns;
|
||||||
context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => {
|
context.countdowns = Object.keys(this.data.countdowns).reduce((acc, key) => {
|
||||||
const countdown = this.data.countdowns[key];
|
const countdown = this.data.countdowns[key];
|
||||||
|
const isLooping = countdown.progress.looping !== CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping;
|
||||||
|
const loopTooltip = isLooping
|
||||||
|
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
|
||||||
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||||
|
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||||
|
: 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].name),
|
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
|
||||||
progress: {
|
progress: {
|
||||||
...countdown.progress,
|
...countdown.progress,
|
||||||
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownTypes[countdown.progress.type].label)
|
typeName: game.i18n.localize(
|
||||||
|
CONFIG.DH.GENERAL.countdownProgressionTypes[countdown.progress.type].label
|
||||||
|
)
|
||||||
},
|
},
|
||||||
editing: this.editingCountdowns.has(key)
|
editing: this.editingCountdowns.has(key),
|
||||||
|
randomizeValid,
|
||||||
|
loopTooltip
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
|
@ -111,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}`, {
|
||||||
|
|
@ -178,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,6 +33,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
actions: {
|
actions: {
|
||||||
toggleViewMode: DhCountdowns.#toggleViewMode,
|
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||||
editCountdowns: DhCountdowns.#editCountdowns,
|
editCountdowns: DhCountdowns.#editCountdowns,
|
||||||
|
loopCountdown: DhCountdowns.#loopCountdown,
|
||||||
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||||
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||||
},
|
},
|
||||||
|
|
@ -81,28 +82,27 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**@inheritdoc */
|
/** Returns countdown data filtered by ownership */
|
||||||
async _onFirstRender(context, options) {
|
#getCountdowns() {
|
||||||
await super._onFirstRender(context, options);
|
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
||||||
this.toggleCollapsedPosition(undefined, !ui.sidebar.expanded);
|
key,
|
||||||
|
countdown,
|
||||||
|
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
|
||||||
|
}));
|
||||||
|
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
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 = Object.keys(setting.countdowns).reduce((acc, key) => {
|
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
||||||
const countdown = setting.countdowns[key];
|
|
||||||
const ownership = DhCountdowns.#getPlayerOwnership(game.user, setting, countdown);
|
|
||||||
if (ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) return acc;
|
|
||||||
|
|
||||||
const playersWithAccess = game.users.reduce((acc, user) => {
|
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||||
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
|
const ownership = DhCountdowns.#getPlayerOwnership(user, setting, countdown);
|
||||||
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
if (!user.isGM && ownership && ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE) {
|
||||||
|
|
@ -111,11 +111,27 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
const nonGmPlayers = game.users.filter(x => !x.isGM);
|
const nonGmPlayers = game.users.filter(x => !x.isGM);
|
||||||
|
|
||||||
|
const countdownEditable = game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER;
|
||||||
|
const isLooping = countdown.progress.looping !== CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping;
|
||||||
|
const loopTooltip = isLooping
|
||||||
|
? countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.increasingLoop'
|
||||||
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
|
? 'DAGGERHEART.UI.Countdowns.decreasingLoop'
|
||||||
|
: 'DAGGERHEART.UI.Countdowns.loop'
|
||||||
|
: null;
|
||||||
|
const loopDisabled =
|
||||||
|
!countdownEditable ||
|
||||||
|
(isLooping && (countdown.progress.current > 0 || countdown.progress.start === '0'));
|
||||||
|
|
||||||
acc[key] = {
|
acc[key] = {
|
||||||
...countdown,
|
...countdown,
|
||||||
editable: game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER,
|
editable: countdownEditable,
|
||||||
playerAccess: playersWithAccess.length !== nonGmPlayers.length ? playersWithAccess : [],
|
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0,
|
||||||
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0
|
shouldLoop: isLooping && countdown.progress.current === 0 && countdown.progress.start > 0,
|
||||||
|
loopDisabled: isLooping ? loopDisabled : null,
|
||||||
|
loopTooltip: isLooping && game.i18n.localize(loopTooltip)
|
||||||
};
|
};
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
@ -130,14 +146,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
: playerOwnership;
|
: playerOwnership;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCollapsedPosition = async (_, collapsed) => {
|
|
||||||
if (!this.element) return;
|
|
||||||
|
|
||||||
this.sidebarCollapsed = collapsed;
|
|
||||||
if (!collapsed) this.element.classList.add('expanded');
|
|
||||||
else this.element.classList.remove('expanded');
|
|
||||||
};
|
|
||||||
|
|
||||||
cooldownRefresh = ({ refreshType }) => {
|
cooldownRefresh = ({ refreshType }) => {
|
||||||
if (refreshType === RefreshType.Countdown) this.render();
|
if (refreshType === RefreshType.Countdown) this.render();
|
||||||
};
|
};
|
||||||
|
|
@ -169,13 +177,46 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
new game.system.api.applications.ui.CountdownEdit().render(true);
|
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async #loopCountdown(_, target) {
|
||||||
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
|
|
||||||
|
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
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 =
|
||||||
|
countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.increasing.id
|
||||||
|
? Number(progressMax) + 1
|
||||||
|
: countdown.progress.looping === CONFIG.DH.GENERAL.countdownLoopingTypes.decreasing.id
|
||||||
|
? Math.max(Number(progressMax) - 1, 0)
|
||||||
|
: progressMax;
|
||||||
|
|
||||||
|
await waitForDiceSoNice(message);
|
||||||
|
await settings.updateSource({
|
||||||
|
[`countdowns.${target.id}.progress`]: {
|
||||||
|
current: newMax,
|
||||||
|
start: newMax
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static async editCountdown(increase, target) {
|
static async editCountdown(increase, target) {
|
||||||
if (!DhCountdowns.canPerformEdit()) return;
|
if (!DhCountdowns.canPerformEdit()) return;
|
||||||
|
|
||||||
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, {
|
||||||
|
|
@ -193,7 +234,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHooks() {
|
setupHooks() {
|
||||||
Hooks.on('collapseSidebar', this.toggleCollapsedPosition.bind());
|
|
||||||
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,16 +241,24 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
||||||
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
/* Opt out of Foundry's standard behavior of closing all application windows marked as UI when Escape is pressed */
|
||||||
if (options.closeKey) return;
|
if (options.closeKey) return;
|
||||||
|
|
||||||
Hooks.off('collapseSidebar', this.toggleCollapsedPosition);
|
|
||||||
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
||||||
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);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,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]);
|
||||||
|
|
@ -229,13 +277,19 @@ 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 };
|
async _onRender(context, options) {
|
||||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
await super._onRender(context, options);
|
||||||
action: socketEvent.Refresh,
|
this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0;
|
||||||
data
|
if (options?.force) {
|
||||||
});
|
document.getElementById('ui-right-column-1')?.appendChild(this.element);
|
||||||
Hooks.callAll(socketEvent.Refresh, data);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
id: 'itemBrowser',
|
id: 'itemBrowser',
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'daggerheart-loader'],
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
|
|
@ -277,7 +277,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
toggleLoader(state) {
|
toggleLoader(state) {
|
||||||
const container = this.element.querySelector('.item-list');
|
const container = this.element.querySelector('.item-list');
|
||||||
return setTimeout(() => {
|
return setTimeout(() => {
|
||||||
container.classList.toggle('loader', state);
|
container.classList.toggle('daggerheart-loader', state);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,7 +294,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
const property = foundry.utils.getProperty(item, field.key);
|
const property = foundry.utils.getProperty(item, field.key);
|
||||||
if (Array.isArray(property)) property.join(', ');
|
if (Array.isArray(property)) property.join(', ');
|
||||||
if (typeof field.format !== 'function') return property ?? '-';
|
if (typeof field.format !== 'function') return property ?? '-';
|
||||||
return field.format(property);
|
return game.i18n.localize(field.format(property));
|
||||||
}
|
}
|
||||||
|
|
||||||
formatChoices(data) {
|
formatChoices(data) {
|
||||||
|
|
@ -430,7 +430,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
|
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
|
||||||
|
|
||||||
const { search } = this.#filteredItems.browser;
|
const { search } = this.#filteredItems.browser;
|
||||||
li.hidden = !(search.has(item.id) && matchesMenu);
|
li.hidden = !((this.#search.browser.query.length === 0 || search.has(item.id)) && matchesMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -469,6 +469,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
static resetFilters() {
|
static resetFilters() {
|
||||||
this.render({ force: true });
|
this.render({ force: true });
|
||||||
|
this.loadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFolderConfig(folder, property = 'columns') {
|
static getFolderConfig(folder, property = 'columns') {
|
||||||
|
|
|
||||||
|
|
@ -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,9 +2,15 @@ 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: {
|
||||||
|
id: 'countdown',
|
||||||
|
name: 'DAGGERHEART.ACTIONS.TYPES.countdown.name',
|
||||||
|
icon: 'fa-hourglass-half',
|
||||||
|
tooltip: 'DAGGERHEART.ACTIONS.TYPES.countdown.tooltip'
|
||||||
|
},
|
||||||
healing: {
|
healing: {
|
||||||
id: 'healing',
|
id: 'healing',
|
||||||
name: 'DAGGERHEART.ACTIONS.TYPES.healing.name',
|
name: 'DAGGERHEART.ACTIONS.TYPES.healing.name',
|
||||||
|
|
|
||||||
|
|
@ -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: [
|
||||||
|
|
@ -611,18 +611,30 @@ export const abilityCosts = {
|
||||||
resource: itemAbilityCosts.resource
|
resource: itemAbilityCosts.resource
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countdownTypes = {
|
export const countdownProgressionTypes = {
|
||||||
spotlight: {
|
actionRoll: {
|
||||||
id: 'spotlight',
|
id: 'actionRoll',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
label: 'DAGGERHEART.CONFIG.CountdownType.actionRoll'
|
||||||
},
|
},
|
||||||
characterAttack: {
|
characterAttack: {
|
||||||
id: 'characterAttack',
|
id: 'characterAttack',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
||||||
},
|
},
|
||||||
|
characterSpotlight: {
|
||||||
|
id: 'characterSpotlight',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CountdownType.characterSpotlight'
|
||||||
|
},
|
||||||
custom: {
|
custom: {
|
||||||
id: 'custom',
|
id: 'custom',
|
||||||
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
label: 'DAGGERHEART.CONFIG.CountdownType.custom'
|
||||||
|
},
|
||||||
|
fear: {
|
||||||
|
id: 'fear',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CountdownType.fear'
|
||||||
|
},
|
||||||
|
spotlight: {
|
||||||
|
id: 'spotlight',
|
||||||
|
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const rollTypes = {
|
export const rollTypes = {
|
||||||
|
|
@ -673,11 +685,30 @@ export const simpleOwnershiplevels = {
|
||||||
export const countdownBaseTypes = {
|
export const countdownBaseTypes = {
|
||||||
narrative: {
|
narrative: {
|
||||||
id: 'narrative',
|
id: 'narrative',
|
||||||
name: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
|
||||||
},
|
},
|
||||||
encounter: {
|
encounter: {
|
||||||
id: 'encounter',
|
id: 'encounter',
|
||||||
name: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter'
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.encounter'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const countdownLoopingTypes = {
|
||||||
|
noLooping: {
|
||||||
|
id: 'noLooping',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.noLooping'
|
||||||
|
},
|
||||||
|
looping: {
|
||||||
|
id: 'looping',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.looping'
|
||||||
|
},
|
||||||
|
increasing: {
|
||||||
|
id: 'increasing',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.increasing'
|
||||||
|
},
|
||||||
|
decreasing: {
|
||||||
|
id: 'decreasing',
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.loopingTypes.decreasing'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
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;
|
||||||
|
|
@ -363,7 +363,7 @@ export const typeConfig = {
|
||||||
{
|
{
|
||||||
key: 'system.linkedClass',
|
key: 'system.linkedClass',
|
||||||
label: 'Class',
|
label: 'Class',
|
||||||
format: linkedClass => linkedClass.name
|
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'system.spellcastingTrait',
|
key: 'system.spellcastingTrait',
|
||||||
|
|
@ -375,7 +375,9 @@ export const typeConfig = {
|
||||||
key: 'system.linkedClass.uuid',
|
key: 'system.linkedClass.uuid',
|
||||||
label: 'Class',
|
label: 'Class',
|
||||||
choices: items => {
|
choices: items => {
|
||||||
const list = items.map(item => ({
|
const list = items
|
||||||
|
.filter(item => item.system.linkedClass)
|
||||||
|
.map(item => ({
|
||||||
value: item.system.linkedClass.uuid,
|
value: item.system.linkedClass.uuid,
|
||||||
label: item.system.linkedClass.name
|
label: item.system.linkedClass.name
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1547,3 +1521,8 @@ export const beastformTypes = {
|
||||||
label: 'DAGGERHEART.CONFIG.BeastformType.hybrid'
|
label: 'DAGGERHEART.CONFIG.BeastformType.hybrid'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const originItemType = {
|
||||||
|
itemCollection: 'itemCollection',
|
||||||
|
restMove: 'restMove'
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ export { default as DhCombat } from './combat.mjs';
|
||||||
export { default as DhCombatant } from './combatant.mjs';
|
export { default as DhCombatant } from './combatant.mjs';
|
||||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||||
|
|
||||||
|
export * as countdowns from './countdowns.mjs';
|
||||||
export * as actions from './action/_module.mjs';
|
export * as actions from './action/_module.mjs';
|
||||||
export * as activeEffects from './activeEffect/_module.mjs';
|
export * as activeEffects from './activeEffect/_module.mjs';
|
||||||
export * as actors from './actor/_module.mjs';
|
export * as actors from './actor/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import AttackAction from './attackAction.mjs';
|
import AttackAction from './attackAction.mjs';
|
||||||
import BaseAction from './baseAction.mjs';
|
import BaseAction from './baseAction.mjs';
|
||||||
import BeastformAction from './beastformAction.mjs';
|
import BeastformAction from './beastformAction.mjs';
|
||||||
|
import CountdownAction from './countdownAction.mjs';
|
||||||
import DamageAction from './damageAction.mjs';
|
import DamageAction from './damageAction.mjs';
|
||||||
import EffectAction from './effectAction.mjs';
|
import EffectAction from './effectAction.mjs';
|
||||||
import HealingAction from './healingAction.mjs';
|
import HealingAction from './healingAction.mjs';
|
||||||
|
|
@ -10,6 +11,7 @@ import SummonAction from './summonAction.mjs';
|
||||||
export const actionsTypes = {
|
export const actionsTypes = {
|
||||||
base: BaseAction,
|
base: BaseAction,
|
||||||
attack: AttackAction,
|
attack: AttackAction,
|
||||||
|
countdown: CountdownAction,
|
||||||
damage: DamageAction,
|
damage: DamageAction,
|
||||||
healing: HealingAction,
|
healing: HealingAction,
|
||||||
summon: SummonAction,
|
summon: SummonAction,
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,10 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
async use(event, options) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, options);
|
const result = await super.use(event, options);
|
||||||
|
|
||||||
|
if (result.message.system.action.roll?.type === 'attack') {
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import DhpActor from '../../documents/actor.mjs';
|
import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
import { ActionMixin } from '../fields/actionField.mjs';
|
import { ActionMixin } from '../fields/actionField.mjs';
|
||||||
|
import { originItemField } from '../chat-message/actorRoll.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -21,16 +22,19 @@ 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 }),
|
||||||
chatDisplay: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.ACTIONS.Config.displayInChat' }),
|
chatDisplay: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.ACTIONS.Config.displayInChat' }),
|
||||||
|
originItem: originItemField(),
|
||||||
actionType: new fields.StringField({
|
actionType: new fields.StringField({
|
||||||
choices: CONFIG.DH.ITEM.actionTypes,
|
choices: CONFIG.DH.ITEM.actionTypes,
|
||||||
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 => {
|
||||||
|
|
@ -41,6 +45,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return schemaFields;
|
return schemaFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default values to supply to schema fields when they are created in the actionConfig. Defined by implementing classes.
|
||||||
|
*/
|
||||||
|
get defaultValues() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
||||||
*
|
*
|
||||||
|
|
@ -85,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -149,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;
|
||||||
|
|
@ -183,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;
|
||||||
|
|
||||||
|
|
@ -198,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,6 +227,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
||||||
source: {
|
source: {
|
||||||
item: this.item._id,
|
item: this.item._id,
|
||||||
|
originItem: this.originItem,
|
||||||
action: this._id,
|
action: this._id,
|
||||||
actor: this.actor.uuid
|
actor: this.actor.uuid
|
||||||
},
|
},
|
||||||
|
|
@ -228,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;
|
||||||
}
|
}
|
||||||
|
|
@ -311,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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
49
module/data/action/countdownAction.mjs
Normal file
49
module/data/action/countdownAction.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
|
export default class DhCountdownAction extends DHBaseAction {
|
||||||
|
static extraSchemas = [...super.extraSchemas, 'countdown'];
|
||||||
|
|
||||||
|
get defaultValues() {
|
||||||
|
return {
|
||||||
|
...super.defaultValues,
|
||||||
|
countdown: {
|
||||||
|
name: this.parent.parent.name,
|
||||||
|
img: this.img,
|
||||||
|
progress: {
|
||||||
|
startFormula: '1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static getSourceConfig(parent) {
|
||||||
|
const updateSource = game.system.api.data.actions.actionsTypes.base.getSourceConfig(parent);
|
||||||
|
updateSource.name = game.i18n.localize('DAGGERHEART.ACTIONS.Config.countdown.startCountdown');
|
||||||
|
updateSource['countdown'] = [
|
||||||
|
{
|
||||||
|
...game.system.api.data.countdowns.DhCountdown.defaultCountdown(),
|
||||||
|
name: parent.parent.name,
|
||||||
|
img: parent.parent.img,
|
||||||
|
progress: {
|
||||||
|
startFormula: '1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
characterTokenData: new fields.SchemaField({
|
characterTokenData: new fields.SchemaField({
|
||||||
|
usesDynamicToken: new fields.BooleanField({ initial: false }),
|
||||||
tokenImg: new fields.FilePathField({
|
tokenImg: new fields.FilePathField({
|
||||||
categories: ['IMAGE'],
|
categories: ['IMAGE'],
|
||||||
base64: false,
|
base64: false,
|
||||||
nullable: true
|
nullable: true,
|
||||||
|
wildcard: true
|
||||||
}),
|
}),
|
||||||
tokenRingImg: new fields.FilePathField({
|
tokenRingImg: new fields.FilePathField({
|
||||||
initial: 'icons/svg/mystery-man.svg',
|
initial: 'icons/svg/mystery-man.svg',
|
||||||
|
|
@ -17,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()),
|
||||||
|
|
@ -27,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;
|
||||||
|
|
||||||
|
|
@ -38,20 +48,57 @@ export default class BeastformEffect extends BaseEffect {
|
||||||
|
|
||||||
async _preDelete() {
|
async _preDelete() {
|
||||||
if (this.parent.parent.type === 'character') {
|
if (this.parent.parent.type === 'character') {
|
||||||
const update = {
|
const baseUpdate = {
|
||||||
height: this.characterTokenData.tokenSize.height,
|
height: this.characterTokenData.tokenSize.height,
|
||||||
width: this.characterTokenData.tokenSize.width,
|
width: this.characterTokenData.tokenSize.width
|
||||||
|
};
|
||||||
|
const update = {
|
||||||
|
...baseUpdate,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.characterTokenData.tokenImg
|
src: this.characterTokenData.tokenImg
|
||||||
},
|
},
|
||||||
ring: {
|
ring: {
|
||||||
|
enabled: this.characterTokenData.usesDynamicToken,
|
||||||
subject: {
|
subject: {
|
||||||
texture: this.characterTokenData.tokenRingImg
|
texture: this.characterTokenData.tokenRingImg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateActorTokens(this.parent.parent, update);
|
const updateToken = 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 },
|
||||||
|
baseUpdate.width,
|
||||||
|
baseUpdate.height
|
||||||
|
);
|
||||||
|
|
||||||
|
x = positionData.x;
|
||||||
|
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 this.parent.parent.deleteEmbeddedDocuments('Item', this.featureIds);
|
await this.parent.parent.deleteEmbeddedDocuments('Item', this.featureIds);
|
||||||
await this.parent.parent.deleteEmbeddedDocuments('ActiveEffect', this.effectIds);
|
await this.parent.parent.deleteEmbeddedDocuments('ActiveEffect', this.effectIds);
|
||||||
|
|
|
||||||
|
|
@ -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,7 +217,10 @@ 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({
|
||||||
|
damageReduction: {
|
||||||
|
magical: new fields.BooleanField({ initial: false }),
|
||||||
|
physical: new fields.BooleanField({ initial: false }),
|
||||||
maxArmorMarked: new fields.SchemaField({
|
maxArmorMarked: new fields.SchemaField({
|
||||||
value: new fields.NumberField({
|
value: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
|
|
@ -234,7 +237,9 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
stressDamageReduction: new fields.SchemaField({
|
stressDamageReduction: new fields.SchemaField({
|
||||||
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
|
severe: stressDamageReductionRule(
|
||||||
|
'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'
|
||||||
|
),
|
||||||
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
||||||
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
||||||
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
||||||
|
|
@ -245,12 +250,8 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
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 })
|
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) {
|
|
||||||
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);
|
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,12 +684,16 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTags() {
|
_getTags() {
|
||||||
return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter((t) => !!t);
|
return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter(
|
||||||
|
t => !!t
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
@ -102,6 +109,10 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export default class DhParty extends BaseDataActor {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }),
|
partyMembers: new ForeignDocumentUUIDArrayField({ type: 'Actor' }, { prune: true }),
|
||||||
notes: new fields.HTMLField(),
|
notes: new fields.HTMLField(),
|
||||||
gold: new fields.SchemaField({
|
gold: new fields.SchemaField({
|
||||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
coins: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
|
|
@ -25,9 +25,12 @@ export default class DhParty extends BaseDataActor {
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
isItemValid(source) {
|
||||||
|
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type);
|
||||||
|
}
|
||||||
|
|
||||||
prepareBaseData() {
|
prepareBaseData() {
|
||||||
super.prepareBaseData();
|
super.prepareBaseData();
|
||||||
this.partyMembers = this.partyMembers.filter(p => !!p);
|
|
||||||
|
|
||||||
// Register this party to all members
|
// Register this party to all members
|
||||||
if (game.actors.get(this.parent.id) === this.parent) {
|
if (game.actors.get(this.parent.id) === this.parent) {
|
||||||
|
|
@ -37,12 +40,29 @@ 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);
|
||||||
|
|
||||||
// Clear this party from all members that aren't deleted
|
// Clear this party from all members that aren't deleted
|
||||||
for (const member of this.partyMembers) {
|
for (const member of this.partyMembers) {
|
||||||
member.parties?.delete(this.parent);
|
member?.parties?.delete(this.parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,16 @@ const targetsField = () =>
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const originItemField = () =>
|
||||||
|
new fields.SchemaField({
|
||||||
|
type: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.ITEM.originItemType,
|
||||||
|
initial: CONFIG.DH.ITEM.originItemType.itemCollection
|
||||||
|
}),
|
||||||
|
itemPath: new fields.StringField(),
|
||||||
|
actionIndex: new fields.StringField()
|
||||||
|
});
|
||||||
|
|
||||||
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -35,6 +45,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
source: new fields.SchemaField({
|
source: new fields.SchemaField({
|
||||||
actor: new fields.StringField(),
|
actor: new fields.StringField(),
|
||||||
item: new fields.StringField(),
|
item: new fields.StringField(),
|
||||||
|
originItem: originItemField(),
|
||||||
action: new fields.StringField()
|
action: new fields.StringField()
|
||||||
}),
|
}),
|
||||||
damage: new fields.ObjectField(),
|
damage: new fields.ObjectField(),
|
||||||
|
|
@ -51,14 +62,23 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
get actionItem() {
|
get actionItem() {
|
||||||
const actionActor = this.actionActor;
|
const actionActor = this.actionActor;
|
||||||
if (!actionActor || !this.source.item) return null;
|
if (!actionActor || !this.source.item) return null;
|
||||||
return actionActor.items.get(this.source.item);
|
|
||||||
|
switch (this.source.originItem.type) {
|
||||||
|
case CONFIG.DH.ITEM.originItemType.restMove:
|
||||||
|
const restMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
|
||||||
|
return Array.from(foundry.utils.getProperty(restMoves, `${this.source.originItem.itemPath}`).actions)[
|
||||||
|
this.source.originItem.actionIndex
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
const item = actionActor.items.get(this.source.item);
|
||||||
|
return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get action() {
|
get action() {
|
||||||
const actionActor = this.actionActor,
|
const { actionActor, actionItem: itemAction } = this;
|
||||||
actionItem = this.actionItem;
|
|
||||||
if (!this.source.action) return null;
|
if (!this.source.action) return null;
|
||||||
if (actionItem) return actionItem.system.actionsList?.find(a => a.id === this.source.action);
|
if (itemAction) return itemAction;
|
||||||
else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack;
|
else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import FormulaField from './fields/formulaField.mjs';
|
||||||
|
|
||||||
export default class DhCountdowns extends foundry.abstract.DataModel {
|
export default class DhCountdowns extends foundry.abstract.DataModel {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -105,8 +107,8 @@ class DhOldCountdown extends foundry.abstract.DataModel {
|
||||||
type: new fields.SchemaField({
|
type: new fields.SchemaField({
|
||||||
value: new fields.StringField({
|
value: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
|
||||||
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
|
||||||
label: 'DAGGERHEART.GENERAL.type'
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
}),
|
}),
|
||||||
label: new fields.StringField({
|
label: new fields.StringField({
|
||||||
|
|
@ -165,16 +167,27 @@ 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 fields.NumberField({
|
start: new fields.NumberField({
|
||||||
required: true,
|
required: true,
|
||||||
integer: 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
|
||||||
|
}),
|
||||||
|
looping: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.countdownLoopingTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.countdownLoopingTypes.noLooping.id,
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.looping.label'
|
||||||
}),
|
}),
|
||||||
type: new fields.StringField({
|
type: new fields.StringField({
|
||||||
required: true,
|
required: true,
|
||||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
|
||||||
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
|
||||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -198,7 +211,7 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
||||||
ownership: ownership,
|
ownership: ownership,
|
||||||
progress: {
|
progress: {
|
||||||
current: 1,
|
current: 1,
|
||||||
max: 1
|
start: 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -217,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export { default as CostField } from './costField.mjs';
|
export { default as CostField } from './costField.mjs';
|
||||||
|
export { default as CountdownField } from './countdownField.mjs';
|
||||||
export { default as UsesField } from './usesField.mjs';
|
export { default as UsesField } from './usesField.mjs';
|
||||||
export { default as RangeField } from './rangeField.mjs';
|
export { default as RangeField } from './rangeField.mjs';
|
||||||
export { default as TargetField } from './targetField.mjs';
|
export { default as TargetField } from './targetField.mjs';
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,6 @@ export default class BeastformField extends fields.SchemaField {
|
||||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
*/
|
*/
|
||||||
static async execute(config) {
|
static async execute(config) {
|
||||||
// Should not be useful anymore here
|
|
||||||
await BeastformField.handleActiveTransformations.call(this);
|
|
||||||
|
|
||||||
const { selected, evolved, hybrid } = await BeastformDialog.configure(config, this.item);
|
const { selected, evolved, hybrid } = await BeastformDialog.configure(config, this.item);
|
||||||
if (!selected) return false;
|
if (!selected) return false;
|
||||||
|
|
||||||
|
|
@ -79,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');
|
||||||
|
|
@ -95,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
110
module/data/fields/action/countdownField.mjs
Normal file
110
module/data/fields/action/countdownField.mjs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
|
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class CountdownField extends fields.ArrayField {
|
||||||
|
constructor(options = {}, context = {}) {
|
||||||
|
const element = new fields.SchemaField({
|
||||||
|
...game.system.api.data.countdowns.DhCountdown.defineSchema(),
|
||||||
|
type: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.countdownBaseTypes,
|
||||||
|
initial: CONFIG.DH.GENERAL.countdownBaseTypes.encounter.id,
|
||||||
|
label: 'DAGGERHEART.GENERAL.type'
|
||||||
|
}),
|
||||||
|
name: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
initial: game.i18n.localize('DAGGERHEART.APPLICATIONS.Countdown.newCountdown'),
|
||||||
|
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.name.label'
|
||||||
|
}),
|
||||||
|
defaultOwnership: new fields.NumberField({
|
||||||
|
required: true,
|
||||||
|
choices: CONFIG.DH.GENERAL.simpleOwnershiplevels,
|
||||||
|
initial: CONST.DOCUMENT_OWNERSHIP_LEVELS.INHERIT,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Config.countdown.defaultOwnership'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
super(element, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Countdown Action Workflow part.
|
||||||
|
* Must be called within Action context or similar. Requires a GM online to edit the game setting for countdowns.
|
||||||
|
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
|
*/
|
||||||
|
static async execute(config) {
|
||||||
|
const noGM = !game.users.find(x => x.isGM && x.active);
|
||||||
|
if (noGM) {
|
||||||
|
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = { countdowns: {} };
|
||||||
|
const countdownMessages = [];
|
||||||
|
for (let countdown of config.countdowns) {
|
||||||
|
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()] = {
|
||||||
|
...countdown,
|
||||||
|
progress: {
|
||||||
|
...countdown.progress,
|
||||||
|
current: countdownStart,
|
||||||
|
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(
|
||||||
|
GMUpdateEvent.UpdateCountdowns,
|
||||||
|
async () => {
|
||||||
|
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||||
|
await countdownSetting.updateSource(data);
|
||||||
|
await game.settings.set(
|
||||||
|
CONFIG.DH.id,
|
||||||
|
CONFIG.DH.SETTINGS.gameSettings.Countdowns,
|
||||||
|
countdownSetting.toObject()
|
||||||
|
),
|
||||||
|
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||||
|
action: socketEvent.Refresh,
|
||||||
|
data: { refreshType: RefreshType.Countdown }
|
||||||
|
});
|
||||||
|
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
refreshType: RefreshType.Countdown
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update Action Workflow config object.
|
||||||
|
* Must be called within Action context.
|
||||||
|
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||||
|
*/
|
||||||
|
prepareConfig(config) {
|
||||||
|
config.countdowns = this.countdown;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -98,7 +98,9 @@ export default class DamageField extends fields.SchemaField {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = game.scenes.find(x => x.active).tokens.find(x => x.id === target.id);
|
const token = target.id
|
||||||
|
? game.scenes.find(x => x.active).tokens.find(x => x.id === target.id)
|
||||||
|
: actor.prototypeToken;
|
||||||
if (config.hasHealing)
|
if (config.hasHealing)
|
||||||
damagePromises.push(
|
damagePromises.push(
|
||||||
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))
|
||||||
|
|
|
||||||
|
|
@ -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,8 +124,7 @@ 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: {
|
||||||
|
|
@ -140,13 +139,6 @@ export default class SaveField extends fields.SchemaField {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
await chatMessage.update(changes);
|
await chatMessage.update(changes);
|
||||||
// }, 100);
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,13 @@ export default class TargetField extends fields.SchemaField {
|
||||||
if (!this.target?.type) return (config.targets = []);
|
if (!this.target?.type) return (config.targets = []);
|
||||||
config.hasTarget = true;
|
config.hasTarget = true;
|
||||||
let targets;
|
let targets;
|
||||||
// If the Action is configured as self-targeted, set targets as the owner.
|
// 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));
|
||||||
|
|
@ -72,17 +75,17 @@ export default class TargetField extends fields.SchemaField {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format actor to useful datas for Action roll workflow.
|
* Format actor to useful datas for Action roll workflow.
|
||||||
* @param {*} actor Actor object to format.
|
* @param {*} token Token object to format.
|
||||||
* @returns {*} Formatted Actor.
|
* @returns {*} Formatted Actor.
|
||||||
*/
|
*/
|
||||||
static formatTarget(actor) {
|
static formatTarget(token) {
|
||||||
return {
|
return {
|
||||||
id: actor.id,
|
id: token.id,
|
||||||
actorId: actor.actor.uuid,
|
actorId: token.actor.uuid,
|
||||||
name: actor.actor.name,
|
name: token.name,
|
||||||
img: actor.actor.img,
|
img: token.actor.img,
|
||||||
difficulty: actor.actor.system.difficulty,
|
difficulty: token.actor.system.difficulty,
|
||||||
evasion: actor.actor.system.evasion,
|
evasion: token.actor.system.evasion,
|
||||||
saved: {
|
saved: {
|
||||||
value: null,
|
value: null,
|
||||||
success: null
|
success: null
|
||||||
|
|
|
||||||
|
|
@ -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
|
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(
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ export default class ForeignDocumentUUIDArrayField extends foundry.data.fields.A
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
initialize(value, model, options = {}) {
|
initialize(value, model, options = {}) {
|
||||||
const v = super.initialize(value, model, options);
|
const v = super.initialize(value, model, options);
|
||||||
return () => v.map(entry => (typeof entry === 'function' ? entry() : entry));
|
return () => {
|
||||||
|
const data = v.map(entry => (typeof entry === 'function' ? entry() : entry));
|
||||||
|
return this.options.prune ? data.filter(d => !!d) : data;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 })
|
||||||
}),
|
}),
|
||||||
|
|
@ -160,7 +166,6 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject())
|
this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject())
|
||||||
);
|
);
|
||||||
|
|
||||||
const tokenImages = await this.parent.parent.getTokenImages();
|
|
||||||
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
|
||||||
await beastformEffect.updateSource({
|
await beastformEffect.updateSource({
|
||||||
changes: [
|
changes: [
|
||||||
|
|
@ -175,7 +180,8 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
],
|
],
|
||||||
system: {
|
system: {
|
||||||
characterTokenData: {
|
characterTokenData: {
|
||||||
tokenImg: tokenImages[0],
|
usesDynamicToken: this.parent.parent.prototypeToken.ring.enabled,
|
||||||
|
tokenImg: this.parent.parent.prototypeToken.texture.src,
|
||||||
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
|
||||||
tokenSize: {
|
tokenSize: {
|
||||||
height: this.parent.parent.prototypeToken.height,
|
height: this.parent.parent.prototypeToken.height,
|
||||||
|
|
@ -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()]);
|
||||||
|
|
||||||
await updateActorTokens(this.parent.parent, {
|
const autoTokenSize =
|
||||||
height: this.tokenSize.height,
|
this.tokenSize.size !== 'custom'
|
||||||
width: this.tokenSize.width,
|
? 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 = {
|
||||||
|
height,
|
||||||
|
width,
|
||||||
texture: {
|
texture: {
|
||||||
src: this.tokenImg
|
src: this.tokenImg
|
||||||
},
|
},
|
||||||
|
|
@ -201,22 +216,37 @@ export default class DHBeastform extends BaseDataItem {
|
||||||
texture: this.tokenRingImg
|
texture: this.tokenRingImg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
x = positionData.x;
|
||||||
|
y = positionData.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prototypeTokenUpdate,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
flags: {
|
||||||
|
daggerheart: {
|
||||||
|
beastformTokenImg: token.texture.src,
|
||||||
|
beastformSubjectTexture: token.ring.subject.texture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onCreate(_data, _options, userId) {
|
|
||||||
if (userId !== game.user.id) return;
|
|
||||||
|
|
||||||
if (!this.parent.effects.find(x => x.type === 'beastform')) {
|
|
||||||
this.parent.createEmbeddedDocuments('ActiveEffect', [
|
|
||||||
{
|
|
||||||
type: 'beastform',
|
|
||||||
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
|
|
||||||
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
||||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
countdownAutomation: new fields.BooleanField({
|
||||||
|
required: true,
|
||||||
|
initial: true,
|
||||||
|
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdownAutomation.label'
|
||||||
|
}),
|
||||||
levelupAuto: new fields.BooleanField({
|
levelupAuto: new fields.BooleanField({
|
||||||
required: true,
|
required: true,
|
||||||
initial: true,
|
initial: true,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,17 @@
|
||||||
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, icon) =>
|
||||||
|
new foundry.data.fields.SchemaField({
|
||||||
|
enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }),
|
||||||
|
label: new foundry.data.fields.StringField({
|
||||||
|
required: true,
|
||||||
|
initial,
|
||||||
|
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 {
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -29,37 +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]
|
||||||
}),
|
}),
|
||||||
currency: new fields.SchemaField({
|
tokenSizes: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({
|
tiny: new fields.NumberField({
|
||||||
required: true,
|
integer: false,
|
||||||
initial: false,
|
initial: 0.5,
|
||||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.enabled'
|
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({
|
||||||
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: new fields.StringField({
|
coins: currencyField(
|
||||||
required: true,
|
'Coins',
|
||||||
initial: 'Coins',
|
'DAGGERHEART.SETTINGS.Homebrew.currency.coinName',
|
||||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'
|
'fa-solid fa-coin-front'
|
||||||
}),
|
),
|
||||||
handfuls: new fields.StringField({
|
handfuls: currencyField(
|
||||||
required: true,
|
'Handfuls',
|
||||||
initial: 'Handfuls',
|
'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName',
|
||||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'
|
'fa-solid fa-coins'
|
||||||
}),
|
),
|
||||||
bags: new fields.StringField({
|
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName', 'fa-solid fa-sack'),
|
||||||
required: true,
|
chests: currencyField(
|
||||||
initial: 'Bags',
|
'Chests',
|
||||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'
|
'DAGGERHEART.SETTINGS.Homebrew.currency.chestName',
|
||||||
}),
|
'fa-solid fa-treasure-chest'
|
||||||
chests: new fields.StringField({
|
)
|
||||||
required: true,
|
|
||||||
initial: 'Chests',
|
|
||||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName'
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
restMoves: new fields.SchemaField({
|
restMoves: new fields.SchemaField({
|
||||||
longRest: new fields.SchemaField({
|
longRest: new fields.SchemaField({
|
||||||
|
|
@ -146,4 +180,14 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
_initializeSource(source, options = {}) {
|
||||||
|
source = super._initializeSource(source, options);
|
||||||
|
for (const type of ['coins', 'handfuls', 'bags', 'chests']) {
|
||||||
|
const initial = this.schema.fields.currency.fields[type].getInitialValue();
|
||||||
|
source.currency[type] = foundry.utils.mergeObject(initial, source.currency[type], { inplace: false });
|
||||||
|
}
|
||||||
|
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
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,11 @@ export default class DHRoll extends Roll {
|
||||||
config.hooks = [...this.getHooks(), ''];
|
config.hooks = [...this.getHooks(), ''];
|
||||||
config.dialog ??= {};
|
config.dialog ??= {};
|
||||||
|
|
||||||
const actorIdSplit = config.source.actor.split('.');
|
const actorIdSplit = config.source?.actor?.split('.');
|
||||||
|
if (actorIdSplit) {
|
||||||
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);
|
||||||
config.tagTeamSelected = tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]];
|
config.tagTeamSelected = Boolean(tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]]);
|
||||||
|
}
|
||||||
|
|
||||||
for (const hook of config.hooks) {
|
for (const hook of config.hooks) {
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||||
|
|
@ -113,7 +115,7 @@ export default class DHRoll extends Roll {
|
||||||
game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message);
|
game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) {
|
if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) {
|
||||||
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
await game.dice3d.waitFor3DAnimationByMessageID(message.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,52 +236,3 @@ export default class DHRoll extends Roll {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerRollDiceHooks = () => {
|
|
||||||
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
|
|
||||||
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).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';
|
||||||
|
|
|
||||||
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