mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 19:51:08 +01:00
Compare commits
133 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 |
1409 changed files with 14573 additions and 25125 deletions
314
daggerheart.mjs
314
daggerheart.mjs
|
|
@ -17,12 +17,64 @@ import {
|
|||
socketRegistration
|
||||
} from './module/systemRegistration/_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 TemplateManager from './module/documents/templateManager.mjs';
|
||||
|
||||
CONFIG.DH = SYSTEM;
|
||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||
|
||||
CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll];
|
||||
CONFIG.Dice.daggerheart = {
|
||||
DHRoll: DHRoll,
|
||||
DualityRoll: DualityRoll,
|
||||
D20Roll: D20Roll,
|
||||
DamageRoll: DamageRoll
|
||||
};
|
||||
|
||||
CONFIG.Actor.documentClass = documents.DhpActor;
|
||||
CONFIG.Actor.dataModels = models.actors.config;
|
||||
|
||||
CONFIG.Item.documentClass = documents.DHItem;
|
||||
CONFIG.Item.dataModels = models.items.config;
|
||||
|
||||
CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
|
||||
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
|
||||
|
||||
CONFIG.Combat.documentClass = documents.DhpCombat;
|
||||
CONFIG.Combat.dataModels = { base: models.DhCombat };
|
||||
CONFIG.Combatant.documentClass = documents.DHCombatant;
|
||||
CONFIG.Combatant.dataModels = { base: models.DhCombatant };
|
||||
|
||||
CONFIG.ChatMessage.dataModels = models.chatMessages.config;
|
||||
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
|
||||
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';
|
||||
|
||||
CONFIG.Canvas.rulerClass = placeables.DhRuler;
|
||||
CONFIG.Canvas.layers.templates.layerClass = placeables.DhTemplateLayer;
|
||||
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
|
||||
|
||||
CONFIG.Scene.documentClass = documents.DhScene;
|
||||
|
||||
CONFIG.Token.documentClass = documents.DhToken;
|
||||
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
|
||||
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
|
||||
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
|
||||
|
||||
CONFIG.ui.combat = applications.ui.DhCombatTracker;
|
||||
CONFIG.ui.chat = applications.ui.DhChatLog;
|
||||
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
|
||||
CONFIG.ui.hotbar = applications.ui.DhHotbar;
|
||||
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
|
||||
CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
|
||||
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
|
||||
CONFIG.ui.resources = applications.ui.DhFearTracker;
|
||||
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
|
||||
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
|
||||
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
|
||||
CONFIG.ux.TemplateManager = new TemplateManager();
|
||||
|
||||
Hooks.once('init', () => {
|
||||
CONFIG.DH = SYSTEM;
|
||||
game.system.api = {
|
||||
applications,
|
||||
data,
|
||||
|
|
@ -32,67 +84,102 @@ Hooks.once('init', () => {
|
|||
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;
|
||||
CONFIG.Token.documentClass = documents.DhToken;
|
||||
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
|
||||
DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
|
||||
DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
|
||||
makeDefault: true
|
||||
});
|
||||
|
||||
CONFIG.Item.documentClass = documents.DHItem;
|
||||
|
||||
//Registering the Item DataModel
|
||||
CONFIG.Item.dataModels = models.items.config;
|
||||
const sheetLabel = typePath => () =>
|
||||
game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
|
||||
type: game.i18n.localize(typePath)
|
||||
});
|
||||
|
||||
const { Items, Actors } = foundry.documents.collections;
|
||||
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.Community, { types: ['community'], makeDefault: true });
|
||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, { types: ['class'], makeDefault: true });
|
||||
Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, { types: ['subclass'], makeDefault: true });
|
||||
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.Ancestry, {
|
||||
types: ['ancestry'],
|
||||
makeDefault: true,
|
||||
label: sheetLabel('TYPES.Item.ancestry')
|
||||
});
|
||||
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, {
|
||||
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.registerSheet(SYSTEM.id, applications.sheets.actors.Character, { types: ['character'], makeDefault: true });
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, { types: ['companion'], makeDefault: true });
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, { types: ['adversary'], makeDefault: true });
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, {
|
||||
types: ['character'],
|
||||
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, {
|
||||
types: ['environment'],
|
||||
makeDefault: true
|
||||
makeDefault: true,
|
||||
label: sheetLabel('TYPES.Actor.environment')
|
||||
});
|
||||
Actors.registerSheet(SYSTEM.id, applications.sheets.actors.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(
|
||||
CONFIG.ActiveEffect.documentClass,
|
||||
'core',
|
||||
|
|
@ -103,50 +190,20 @@ Hooks.once('init', () => {
|
|||
SYSTEM.id,
|
||||
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);
|
||||
|
||||
// Make Compendium Dialog resizable
|
||||
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, {
|
||||
makeDefault: true,
|
||||
label: 'Daggerheart'
|
||||
label: sheetLabel('DOCUMENT.Scene')
|
||||
});
|
||||
|
||||
settingsRegistration.registerDHSettings();
|
||||
|
|
@ -176,11 +233,13 @@ Hooks.on('ready', async () => {
|
|||
ui.countdowns.render({ force: true });
|
||||
}
|
||||
|
||||
ui.effectsDisplay = new CONFIG.ui.effectsDisplay();
|
||||
ui.effectsDisplay.render({ force: true });
|
||||
|
||||
if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
|
||||
ui.compendiumBrowser = new applications.ui.ItemBrowser();
|
||||
|
||||
socketRegistration.registerSocketHooks();
|
||||
registerRollDiceHooks();
|
||||
socketRegistration.registerUserQueries();
|
||||
|
||||
if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
|
||||
|
|
@ -196,9 +255,9 @@ Hooks.on('ready', async () => {
|
|||
|
||||
Hooks.once('dicesoniceready', () => {});
|
||||
|
||||
Hooks.on('renderChatMessageHTML', (_, element, message) => {
|
||||
Hooks.on('renderChatMessageHTML', (document, 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));
|
||||
});
|
||||
|
||||
|
|
@ -251,51 +310,70 @@ Hooks.on('chatMessage', (_, message) => {
|
|||
}
|
||||
});
|
||||
|
||||
Hooks.on('moveToken', async (movedToken, data) => {
|
||||
const updateActorsRangeDependentEffects = async token => {
|
||||
const rangeMeasurement = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||
).rangeMeasurement;
|
||||
|
||||
for (let effect of token.actor?.allApplicableEffects() ?? []) {
|
||||
if (!effect.system.rangeDependence?.enabled) continue;
|
||||
const { target, range, type } = effect.system.rangeDependence;
|
||||
|
||||
// If there are no targets, assume false. Otherwise, start with the effect enabled.
|
||||
let enabledEffect = game.user.targets.size !== 0;
|
||||
// Expect all targets to meet the rangeDependence requirements
|
||||
for (let userTarget of game.user.targets) {
|
||||
const disposition = userTarget.document.disposition;
|
||||
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) {
|
||||
enabledEffect = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Get required distance and special case 5 feet to test adjacency
|
||||
const required = rangeMeasurement[range];
|
||||
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
||||
const inRange =
|
||||
required === 5
|
||||
? userTarget.isAdjacentWith(token.object)
|
||||
: userTarget.distanceTo(token.object) <= required;
|
||||
if (reverse ? inRange : !inRange) {
|
||||
enabledEffect = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await effect.update({ disabled: !enabledEffect });
|
||||
}
|
||||
};
|
||||
|
||||
const updateAllRangeDependentEffects = async () => {
|
||||
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
|
||||
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(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.variantRules
|
||||
).rangeMeasurement;
|
||||
|
||||
for (let effect of effects.filter(x => x.system.rangeDependence?.enabled)) {
|
||||
const { target, range, type } = effect.system.rangeDependence;
|
||||
if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1))
|
||||
return false;
|
||||
|
||||
const distanceBetween = canvas.grid.measurePath([
|
||||
{ ...movedToken.toObject(), x: data.destination.x, y: data.destination.y },
|
||||
token
|
||||
]).distance;
|
||||
const distance = rangeMeasurement[range];
|
||||
|
||||
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
|
||||
const newDisabled = reverse ? distanceBetween <= distance : distanceBetween > distance;
|
||||
const oldDisabled = effectUpdates[effect.uuid] ? effectUpdates[effect.uuid].disabled : newDisabled;
|
||||
effectUpdates[effect.uuid] = {
|
||||
disabled: oldDisabled || newDisabled,
|
||||
value: effect
|
||||
};
|
||||
const 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);
|
||||
}
|
||||
};
|
||||
|
||||
const effectUpdates = {};
|
||||
for (let token of game.scenes.find(x => x.active).tokens) {
|
||||
if (token.id !== movedToken.id) {
|
||||
await updateEffects(token.disposition, token, rangeDependantEffects, effectUpdates);
|
||||
}
|
||||
|
||||
if (token.actor) await updateEffects(movedToken.disposition, token, token.actor.effects, effectUpdates);
|
||||
}
|
||||
};
|
||||
|
||||
for (let key in effectUpdates) {
|
||||
const effect = effectUpdates[key];
|
||||
await effect.value.update({ disabled: effect.disabled });
|
||||
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);
|
||||
|
||||
Hooks.on('targetToken', () => {
|
||||
debouncedRangeEffectCall();
|
||||
});
|
||||
|
||||
Hooks.on('refreshToken', (_, options) => {
|
||||
if (options.refreshPosition) {
|
||||
debouncedRangeEffectCall();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
191
lang/en.json
191
lang/en.json
|
|
@ -36,6 +36,7 @@
|
|||
},
|
||||
|
||||
"DAGGERHEART": {
|
||||
"CharacterSheet": "Character Sheet",
|
||||
"ACTIONS": {
|
||||
"TYPES": {
|
||||
"attack": {
|
||||
|
|
@ -46,6 +47,10 @@
|
|||
"name": "Beastform",
|
||||
"tooltip": "Shapeshift the user into another form."
|
||||
},
|
||||
"countdown": {
|
||||
"name": "Countdown",
|
||||
"tooltip": "Start a countdown"
|
||||
},
|
||||
"damage": {
|
||||
"name": "Damage",
|
||||
"tooltip": "Direct damage without a roll."
|
||||
|
|
@ -73,6 +78,10 @@
|
|||
"exactHint": "The Character's Tier is used if empty",
|
||||
"label": "Beastform"
|
||||
},
|
||||
"countdown": {
|
||||
"defaultOwnership": "Default Ownership",
|
||||
"startCountdown": "Start Countdown"
|
||||
},
|
||||
"damage": {
|
||||
"multiplier": "Multiplier",
|
||||
"flatMultiplier": "Flat Multiplier"
|
||||
|
|
@ -95,6 +104,7 @@
|
|||
"Settings": {
|
||||
"attackBonus": "Attack Bonus",
|
||||
"attackName": "Attack Name",
|
||||
"criticalThreshold": "Critical Threshold",
|
||||
"includeBase": { "label": "Include Item Damage" },
|
||||
"multiplier": "Multiplier",
|
||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||
|
|
@ -122,7 +132,8 @@
|
|||
"RangeDependance": {
|
||||
"hint": "Settings for an optional distance at which this effect should activate",
|
||||
"title": "Range Dependant"
|
||||
}
|
||||
},
|
||||
"immuneStatusText": "Immunity: {status}"
|
||||
},
|
||||
"ACTORS": {
|
||||
"Adversary": {
|
||||
|
|
@ -215,6 +226,7 @@
|
|||
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
|
||||
},
|
||||
"viewLevelups": "View Levelups",
|
||||
"viewParty": "View Party",
|
||||
"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?",
|
||||
"cancelBeastform": "Cancel Beastform"
|
||||
|
|
@ -315,25 +327,34 @@
|
|||
"equip": "Equip",
|
||||
"sendToChat": "Send To Chat",
|
||||
"toLoadout": "Send to Loadout",
|
||||
"recall": "Recall",
|
||||
"toVault": "Send to Vault",
|
||||
"unequip": "Unequip",
|
||||
"useItem": "Use Item"
|
||||
},
|
||||
"Countdown": {
|
||||
"addCountdown": "Add Countdown",
|
||||
"loopingTypes": {
|
||||
"noLooping": "No Looping",
|
||||
"looping": "Looping",
|
||||
"increasing": "Increasing",
|
||||
"decreasing": "Decreasing"
|
||||
},
|
||||
"FIELDS": {
|
||||
"countdowns": {
|
||||
"element": {
|
||||
"name": { "label": "Name" },
|
||||
"progress": {
|
||||
"current": { "label": "Current" },
|
||||
"max": { "label": "Max" },
|
||||
"looping": { "label": "Looping" },
|
||||
"start": { "label": "Start" },
|
||||
"startFormula": { "label": "Start Formula" },
|
||||
"type": {
|
||||
"label": { "label": "Label", "hint": "Used for custom" },
|
||||
"value": { "label": "Value" }
|
||||
}
|
||||
},
|
||||
"type": { "label": "Countdown Type" }
|
||||
"type": { "label": "Progression Type" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -355,17 +376,21 @@
|
|||
"newCountdown": "New Countdown",
|
||||
"removeCountdownTitle": "Remove Countdown",
|
||||
"removeCountdownText": "Are you sure you want to remove the countdown: {name}?",
|
||||
"current": "Current",
|
||||
"max": "Max",
|
||||
"currentCountdownValue": "Current: {value}",
|
||||
"currentCountdownMax": "Max: {value}",
|
||||
"current": "Current Value",
|
||||
"start": "Start Value",
|
||||
"startFormula": "Randomized Start Value Formula",
|
||||
"currentCountdownCurrent": "Current: {value}",
|
||||
"currentCountdownStart": "Start: {value}",
|
||||
"category": "Category",
|
||||
"type": "Type",
|
||||
"progressionType": "Progression",
|
||||
"decreasing": "Decreasing",
|
||||
"looping": "Looping",
|
||||
"defaultOwnershipTooltip": "The default player ownership of countdowns",
|
||||
"hideNewCountdowns": "Hide New Countdowns"
|
||||
},
|
||||
"DaggerheartMenu": {
|
||||
"title": "GM Tools"
|
||||
"title": "GM Tools",
|
||||
"refreshFeatures": "Refresh Features"
|
||||
},
|
||||
"DeleteConfirmation": {
|
||||
"title": "Delete {type} - {name}",
|
||||
|
|
@ -379,7 +404,8 @@
|
|||
"stressReduction": "Reduce By Stress",
|
||||
"title": "Damage Reduction",
|
||||
"unncessaryStress": "You don't need to expend stress",
|
||||
"usedMarks": "Used Marks"
|
||||
"usedMarks": "Used Marks",
|
||||
"reduceSeverity": "Severity Reduced By {nr}"
|
||||
},
|
||||
"DeathMove": {
|
||||
"selectMove": "Select Move",
|
||||
|
|
@ -447,6 +473,9 @@
|
|||
"title": "Select Image",
|
||||
"selectImage": "Select Image"
|
||||
},
|
||||
"ItemTransfer": {
|
||||
"transfer": "Transfer"
|
||||
},
|
||||
"Levelup": {
|
||||
"actions": {
|
||||
"creatureComfort": {
|
||||
|
|
@ -479,6 +508,7 @@
|
|||
},
|
||||
"navigateLevel": "To Level {level}",
|
||||
"navigateToLevelup": "Return To Levelup",
|
||||
"finishLevelup": "Finish Levelup",
|
||||
"navigateToSummary": "To Summary",
|
||||
"options": {
|
||||
"trait": "Gain a +1 bonus to two unmarked character traits and mark them.",
|
||||
|
|
@ -558,6 +588,7 @@
|
|||
},
|
||||
"OwnershipSelection": {
|
||||
"title": "Ownership Selection - {name}",
|
||||
"noPlayers": "No players to assign ownership to",
|
||||
"default": "Default Ownership"
|
||||
},
|
||||
"ReactionRoll": {
|
||||
|
|
@ -584,6 +615,9 @@
|
|||
"insufficientHope": "The initiating character doesn't have enough hope",
|
||||
"createTagTeam": "Create TagTeam Roll",
|
||||
"chatMessageRollTitle": "Roll"
|
||||
},
|
||||
"TokenConfig": {
|
||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||
}
|
||||
},
|
||||
"CLASS": {
|
||||
|
|
@ -593,11 +627,6 @@
|
|||
}
|
||||
},
|
||||
"CONFIG": {
|
||||
"ActionType": {
|
||||
"passive": "Passive",
|
||||
"action": "Action",
|
||||
"reaction": "Reaction"
|
||||
},
|
||||
"AdversaryTrait": {
|
||||
"relentless": {
|
||||
"name": "Relentless",
|
||||
|
|
@ -657,6 +686,14 @@
|
|||
"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": {
|
||||
"burning": {
|
||||
"name": "Burning",
|
||||
|
|
@ -880,6 +917,30 @@
|
|||
"evolved": "Evolved",
|
||||
"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": {
|
||||
"oneHanded": "One-Handed",
|
||||
"twoHanded": "Two-Handed"
|
||||
|
|
@ -915,9 +976,12 @@
|
|||
}
|
||||
},
|
||||
"CountdownType": {
|
||||
"spotlight": "Spotlight",
|
||||
"actionRoll": "Action Roll",
|
||||
"characterAttack": "Character Attack",
|
||||
"characterSpotlight": "Character Spotlight",
|
||||
"custom": "Custom",
|
||||
"characterAttack": "Character Attack"
|
||||
"fear": "Fear",
|
||||
"spotlight": "Spotlight"
|
||||
},
|
||||
"DamageType": {
|
||||
"physical": {
|
||||
|
|
@ -971,6 +1035,12 @@
|
|||
"description": ""
|
||||
}
|
||||
},
|
||||
"FeatureForm": {
|
||||
"label": "Feature Form",
|
||||
"passive": "Passive",
|
||||
"action": "Action",
|
||||
"reaction": "Reaction"
|
||||
},
|
||||
"Gold": {
|
||||
"title": "Gold",
|
||||
"coins": "Coins",
|
||||
|
|
@ -1011,7 +1081,8 @@
|
|||
},
|
||||
"ItemResourceType": {
|
||||
"simple": "Simple",
|
||||
"diceValue": "Dice Value"
|
||||
"diceValue": "Dice Value",
|
||||
"die": "Die"
|
||||
},
|
||||
"Range": {
|
||||
"self": {
|
||||
|
|
@ -1083,6 +1154,14 @@
|
|||
"rect": "Rectangle",
|
||||
"ray": "Ray"
|
||||
},
|
||||
"TokenSize": {
|
||||
"tiny": "Tiny",
|
||||
"small": "Small",
|
||||
"medium": "Medium",
|
||||
"large": "Large",
|
||||
"huge": "Huge",
|
||||
"gargantuan": "Gargantuan"
|
||||
},
|
||||
"Traits": {
|
||||
"agility": {
|
||||
"name": "Agility",
|
||||
|
|
@ -1192,7 +1271,7 @@
|
|||
},
|
||||
"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": {
|
||||
"burn": {
|
||||
"name": "Burn",
|
||||
|
|
@ -1723,7 +1802,9 @@
|
|||
"label": "Long Rest: Bonus Long Rest Moves",
|
||||
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
|
||||
}
|
||||
}
|
||||
},
|
||||
"target": "Target",
|
||||
"targetSelf": "Self"
|
||||
},
|
||||
"maxLoadout": {
|
||||
"label": "Max Loadout Cards Bonus"
|
||||
|
|
@ -1738,6 +1819,7 @@
|
|||
"plural": "Costs"
|
||||
},
|
||||
"Damage": {
|
||||
"massive": "Massive",
|
||||
"severe": "Severe",
|
||||
"major": "Major",
|
||||
"minor": "Minor",
|
||||
|
|
@ -2003,6 +2085,7 @@
|
|||
"basics": "Basics",
|
||||
"bonus": "Bonus",
|
||||
"burden": "Burden",
|
||||
"condition": "Condition",
|
||||
"continue": "Continue",
|
||||
"criticalSuccess": "Critical Success",
|
||||
"criticalShort": "Critical",
|
||||
|
|
@ -2029,6 +2112,7 @@
|
|||
"fear": "Fear",
|
||||
"features": "Features",
|
||||
"formula": "Formula",
|
||||
"general": "General",
|
||||
"gm": "GM",
|
||||
"healing": "Healing",
|
||||
"healingRoll": "Healing Roll",
|
||||
|
|
@ -2043,6 +2127,7 @@
|
|||
},
|
||||
"hope": "Hope",
|
||||
"hordeHp": "Horde HP",
|
||||
"icon": "Icon",
|
||||
"identify": "Identity",
|
||||
"imagePath": "Image Path",
|
||||
"inactiveEffects": "Inactive Effects",
|
||||
|
|
@ -2064,6 +2149,7 @@
|
|||
"missingDragDropThing": "Drop {thing} here",
|
||||
"multiclass": "Multiclass",
|
||||
"newCategory": "New Category",
|
||||
"newThing": "New {thing}",
|
||||
"none": "None",
|
||||
"noTarget": "No current target",
|
||||
"partner": "Partner",
|
||||
|
|
@ -2078,6 +2164,7 @@
|
|||
"recovery": "Recovery",
|
||||
"refresh": "Refresh",
|
||||
"reroll": "Reroll",
|
||||
"rerolled": "Rerolled",
|
||||
"rerollThing": "Reroll {thing}",
|
||||
"resource": "Resource",
|
||||
"roll": "Roll",
|
||||
|
|
@ -2098,10 +2185,12 @@
|
|||
"plural": "Targets"
|
||||
},
|
||||
"title": "Title",
|
||||
"tokenSize": "Token Size",
|
||||
"total": "Total",
|
||||
"traitModifier": "Trait Modifier",
|
||||
"true": "True",
|
||||
"type": "Type",
|
||||
"typeSheet": "System {type} Sheet",
|
||||
"unarmed": "Unarmed",
|
||||
"unarmedAttack": "Unarmed Attack",
|
||||
"unarmored": "Unarmored",
|
||||
|
|
@ -2154,6 +2243,7 @@
|
|||
"tokenRingImg": { "label": "Subject Texture" },
|
||||
"tokenSize": {
|
||||
"placeholder": "Using character dimensions",
|
||||
"disabledPlaceholder": "Set by character size",
|
||||
"height": { "label": "Height" },
|
||||
"width": { "label": "Width" }
|
||||
},
|
||||
|
|
@ -2177,7 +2267,11 @@
|
|||
"evolvedDrag": "Drag a form here to evolve it.",
|
||||
"hybridize": "Hybridize",
|
||||
"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": {
|
||||
"hopeFeatures": "Hope Features",
|
||||
|
|
@ -2286,6 +2380,10 @@
|
|||
"gm": { "label": "GM" },
|
||||
"players": { "label": "Players" }
|
||||
},
|
||||
"countdownAutomation": {
|
||||
"label": "Countdown Automation",
|
||||
"hint": "Automatically progress countdowns based on their progression settings"
|
||||
},
|
||||
"levelupAuto": {
|
||||
"label": "Levelup Automation",
|
||||
"hint": "When you've made your choices and finish levelup, the numerical changes are automatically applied to your character."
|
||||
|
|
@ -2349,8 +2447,8 @@
|
|||
"newDowntimeMove": "Downtime Move",
|
||||
"downtimeMove": "Downtime Move",
|
||||
"armorFeature": "Armor Feature",
|
||||
"weaponFeature": "Weapon Feaure",
|
||||
"newFeature": "New ItemFeature",
|
||||
"weaponFeature": "Weapon Feature",
|
||||
"newFeature": "New Item Feature",
|
||||
"downtimeMoves": "Downtime Moves",
|
||||
"itemFeatures": "Item Features",
|
||||
"nrChoices": "# Moves Per Rest",
|
||||
|
|
@ -2367,9 +2465,12 @@
|
|||
},
|
||||
"currency": {
|
||||
"title": "Currency Overrides",
|
||||
"changeIcon": "Change Currency Icon",
|
||||
"currencyName": "Currency Name",
|
||||
"coinName": "Coin Name",
|
||||
"handfulName": "Handful Name",
|
||||
"iconName": "Icon Name",
|
||||
"iconNameHint": "Icons are from fontawesome",
|
||||
"bagName": "Bag Name",
|
||||
"chestName": "Chest Name"
|
||||
},
|
||||
|
|
@ -2421,7 +2522,8 @@
|
|||
"texture": "Texture",
|
||||
"colorset": "Theme",
|
||||
"material": "Material",
|
||||
"system": "Dice Preset"
|
||||
"system": "Dice Preset",
|
||||
"font": "Font"
|
||||
}
|
||||
},
|
||||
"variantRules": {
|
||||
|
|
@ -2430,6 +2532,11 @@
|
|||
"hint": "Apply variant rules from the Daggerheart system",
|
||||
"name": "Variant Rules",
|
||||
"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": {
|
||||
|
|
@ -2443,6 +2550,10 @@
|
|||
"actionTokens": {
|
||||
"enabled": { "label": "Enabled" },
|
||||
"tokens": { "label": "Tokens" }
|
||||
},
|
||||
"massiveDamage": {
|
||||
"title": "Massive Damage",
|
||||
"enabled": { "label": "Enabled" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -2502,7 +2613,8 @@
|
|||
"abilityCheckTitle": "{ability} Check"
|
||||
},
|
||||
"effectSummary": {
|
||||
"title": "Effects Applied"
|
||||
"title": "Effects Applied",
|
||||
"immunityTo": "Immunity: {immunities}"
|
||||
},
|
||||
"featureTitle": "Class Feature",
|
||||
"groupRoll": {
|
||||
|
|
@ -2514,7 +2626,8 @@
|
|||
"selectMember": "Select a Member",
|
||||
"rerollTitle": "Reroll Group Roll",
|
||||
"rerollContent": "Are you sure you want to reroll your {trait} check?",
|
||||
"rerollTooltip": "Reroll"
|
||||
"rerollTooltip": "Reroll",
|
||||
"wholePartySelected": "The whole party is selected"
|
||||
},
|
||||
"healingRoll": {
|
||||
"title": "Heal - {damage}",
|
||||
|
|
@ -2545,7 +2658,14 @@
|
|||
"Countdowns": {
|
||||
"title": "Countdowns",
|
||||
"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": {
|
||||
"title": "Daggerheart Compendium Browser",
|
||||
|
|
@ -2623,7 +2743,7 @@
|
|||
"cardTooHighLevel": "The card is too high level!",
|
||||
"duplicateCard": "You cannot select the same card more than once.",
|
||||
"duplicateCharacter": "This actor is already registered in the party members list.",
|
||||
"onlyCharactersInPartySheet": "You can only drag characters, companions and adverasries to the party sheet.",
|
||||
"onlyCharactersInPartySheet": "You can only drag characters, companions and adversaries to the party sheet.",
|
||||
"notPrimary": "The weapon is not a primary weapon!",
|
||||
"notSecondary": "The weapon is not a secondary weapon!",
|
||||
"itemTooHighTier": "The item must be from Tier1",
|
||||
|
|
@ -2661,9 +2781,17 @@
|
|||
"gmRequired": "This action requires an online GM",
|
||||
"gmOnly": "This can only be accessed by the GM",
|
||||
"noActorOwnership": "You do not have permissions for this character",
|
||||
"documentIsMissing": "The {documentType} is missing from the world."
|
||||
"documentIsMissing": "The {documentType} is missing from the world.",
|
||||
"tokenActorMissing": "{name} is missing an Actor",
|
||||
"tokenActorsMissing": "[{names}] missing Actors"
|
||||
},
|
||||
"Sidebar": {
|
||||
"actorDirectory": {
|
||||
"tier": "Tier {tier} {type}",
|
||||
"character": "Level {level} Character",
|
||||
"companion": "Level {level} - {partner}",
|
||||
"companionNoPartner": "No Partner"
|
||||
},
|
||||
"daggerheartMenu": {
|
||||
"title": "Daggerheart Menu",
|
||||
"startSession": "Start Session",
|
||||
|
|
@ -2697,7 +2825,10 @@
|
|||
"rightClickExtand": "Right-Click to extand",
|
||||
"companionPartnerLevelBlock": "The companion needs an assigned partner to level up.",
|
||||
"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 Downtime } from './downtime.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 OwnershipSelection } from './ownershipSelection.mjs';
|
||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||
|
|
|
|||
|
|
@ -57,7 +57,11 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
/** @inheritDoc */
|
||||
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;
|
||||
return {
|
||||
...(await super._prepareContext(options)),
|
||||
|
|
|
|||
|
|
@ -278,19 +278,26 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
|
|||
'close',
|
||||
async () => {
|
||||
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(
|
||||
app.configData.data.parent,
|
||||
app.selected
|
||||
evolved ?? app.selected
|
||||
);
|
||||
if (data) {
|
||||
if (!data.selectedImage) selected = null;
|
||||
else {
|
||||
if (data.usesDynamicToken) selected.system.tokenRingImg = data.selectedImage;
|
||||
else selected.system.tokenImg = data.selectedImage;
|
||||
const imageSource = evolved ?? selected;
|
||||
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 }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.roll = this.roll;
|
||||
context.rollType = this.roll?.constructor.name;
|
||||
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 => ({
|
||||
id,
|
||||
...experiences[id]
|
||||
|
|
@ -116,14 +116,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
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.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
|
||||
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.tagTeamSelected = this.config.tagTeamSelected;
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
extKey: button.dataset.key,
|
||||
key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
name: this.config.data?.system.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
this.render();
|
||||
|
|
@ -195,9 +195,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
if (this.config.roll) {
|
||||
this.reactionOverride = !this.reactionOverride;
|
||||
this.config.actionType = this.reactionOverride
|
||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
: this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
? null
|
||||
? 'reaction'
|
||||
: this.config.actionType === 'reaction'
|
||||
? 'action'
|
||||
: this.config.actionType;
|
||||
this.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.reject = reject;
|
||||
this.actor = actor;
|
||||
this.damage = damage;
|
||||
this.damageType = damageType;
|
||||
this.rulesDefault = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Automation
|
||||
|
|
@ -57,6 +58,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
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(
|
||||
(acc, 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.offWithToggle.id
|
||||
].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 } =
|
||||
this.getDamageInfo();
|
||||
|
|
@ -173,6 +181,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
|
||||
0
|
||||
);
|
||||
if (this.reduceSeverity) {
|
||||
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
|
||||
}
|
||||
|
||||
if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { refreshIsAllowed } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -91,14 +93,10 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
|
||||
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) {
|
||||
const recoverable = x.system.actions.reduce((acc, action) => {
|
||||
if (
|
||||
action.uses.recovery &&
|
||||
((action.uses.recovery === 'longRest' && !this.shortrest) ||
|
||||
action.uses.recovery === 'shortRest')
|
||||
) {
|
||||
if (refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], action.uses.recovery)) {
|
||||
acc.push({
|
||||
title: x.name,
|
||||
name: action.name,
|
||||
|
|
@ -120,8 +118,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
if (
|
||||
x.system.resource &&
|
||||
x.system.resource.type &&
|
||||
((x.system.resource.recovery === 'longRest') === !this.shortrest ||
|
||||
x.system.resource.recovery === 'shortRest')
|
||||
refreshIsAllowed([this.shortrest ? 'shortRest' : 'longRest'], x.system.resource.recovery)
|
||||
) {
|
||||
acc.push({
|
||||
title: game.i18n.localize(`TYPES.Item.${x.type}`),
|
||||
|
|
@ -184,12 +181,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
.filter(x => category.moves[x].selected)
|
||||
.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}`
|
||||
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 msg = {
|
||||
|
|
@ -209,7 +211,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
|
||||
),
|
||||
actor: { name: this.actor.name, img: this.actor.img },
|
||||
moves: moves
|
||||
moves: moves,
|
||||
characters: characters,
|
||||
selfId: this.actor.uuid
|
||||
}
|
||||
),
|
||||
flags: {
|
||||
|
|
|
|||
62
module/applications/dialogs/itemTransfer.mjs
Normal file
62
module/applications/dialogs/itemTransfer.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class ItemTransferDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(data) {
|
||||
super({});
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return this.data.title;
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'item-transfer'],
|
||||
position: { width: 400, height: 'auto' },
|
||||
window: { icon: 'fa-solid fa-hand-holding-hand' },
|
||||
actions: {
|
||||
finish: ItemTransferDialog.#finish
|
||||
}
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: { template: 'systems/daggerheart/templates/dialogs/item-transfer.hbs', root: true }
|
||||
};
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
return foundry.utils.mergeObject(context, this.data);
|
||||
}
|
||||
|
||||
static async #finish() {
|
||||
this.selected = this.form.elements.quantity.valueAsNumber || null;
|
||||
this.close();
|
||||
}
|
||||
|
||||
static #determineTransferOptions({ originActor, targetActor, item, currency }) {
|
||||
originActor ??= item?.actor;
|
||||
const homebrewKey = CONFIG.DH.SETTINGS.gameSettings.Homebrew;
|
||||
const currencySetting = game.settings.get(CONFIG.DH.id, homebrewKey).currency?.[currency] ?? null;
|
||||
|
||||
return {
|
||||
originActor,
|
||||
targetActor,
|
||||
itemImage: item?.img,
|
||||
currencyIcon: currencySetting?.icon,
|
||||
max: item?.system.quantity ?? originActor.system.gold[currency] ?? 0,
|
||||
title: item?.name ?? currencySetting?.label
|
||||
};
|
||||
}
|
||||
|
||||
static async configure(options) {
|
||||
return new Promise(resolve => {
|
||||
const data = this.#determineTransferOptions(options);
|
||||
if (data.max <= 1) return resolve(data.max);
|
||||
|
||||
const app = new this(data);
|
||||
app.addEventListener('close', () => resolve(app.selected), { once: true });
|
||||
app.render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,6 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
||||
context.ownershipOptions = CONFIG.DH.GENERAL.simpleOwnershiplevels;
|
||||
context.defaultOwnership = this.defaultOwnership;
|
||||
context.ownership = game.users.reduce((acc, user) => {
|
||||
|
|
@ -52,6 +51,7 @@ export default class OwnershipSelection extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
return acc;
|
||||
}, {});
|
||||
context.showOwnership = Boolean(Object.keys(context.ownership).length);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
||||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -76,28 +77,37 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
cost: this.data.initiator.cost
|
||||
};
|
||||
|
||||
context.selectedData = Object.values(context.members).reduce(
|
||||
(acc, member) => {
|
||||
if (!member.roll) return acc;
|
||||
if (member.selected) {
|
||||
acc.result = `${member.roll.system.roll.total} ${member.roll.system.roll.result.label}`;
|
||||
}
|
||||
const selectedMember = Object.values(context.members).find(x => x.selected && x.roll);
|
||||
const selectedIsCritical = selectedMember?.roll?.system?.isCritical;
|
||||
context.selectedData = {
|
||||
result: selectedMember
|
||||
? `${selectedMember.roll.system.roll.total} ${selectedMember.roll.system.roll.result.label}`
|
||||
: null,
|
||||
damageValues: null
|
||||
};
|
||||
|
||||
if (context.usesDamage) {
|
||||
if (!acc.damageValues) acc.damageValues = {};
|
||||
for (let damage of member.damageValues) {
|
||||
if (acc.damageValues[damage.key]) {
|
||||
acc.damageValues[damage.key].total += damage.total;
|
||||
} else {
|
||||
acc.damageValues[damage.key] = foundry.utils.deepClone(damage);
|
||||
}
|
||||
for (const member of Object.values(context.members)) {
|
||||
if (!member.roll) continue;
|
||||
if (context.usesDamage) {
|
||||
if (!context.selectedData.damageValues) context.selectedData.damageValues = {};
|
||||
for (let damage of member.damageValues) {
|
||||
const damageTotal = member.roll.system.isCritical
|
||||
? damage.total
|
||||
: selectedIsCritical
|
||||
? damage.total + (await getCritDamageBonus(member.roll.system.damage[damage.key].formula))
|
||||
: damage.total;
|
||||
if (context.selectedData.damageValues[damage.key]) {
|
||||
context.selectedData.damageValues[damage.key].total += damageTotal;
|
||||
} else {
|
||||
context.selectedData.damageValues[damage.key] = {
|
||||
...foundry.utils.deepClone(damage),
|
||||
total: damageTotal
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ result: null, damageValues: null }
|
||||
);
|
||||
context.showResult = Object.values(context.members).reduce((enabled, member) => {
|
||||
if (!member.roll) return enabled;
|
||||
if (context.usesDamage) {
|
||||
|
|
@ -201,21 +211,41 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
.map(key => game.messages.get(this.data.members[key].messageId));
|
||||
|
||||
const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
|
||||
const criticalRoll = systemData.roll.isCritical;
|
||||
for (let roll of secondaryRolls) {
|
||||
if (roll.system.hasDamage) {
|
||||
for (let key in roll.system.damage) {
|
||||
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]) {
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts = [...systemData.damage[key].parts, ...damage.parts];
|
||||
if (!roll.system.isCritical && criticalRoll) {
|
||||
for (let part of updatedDamageParts) {
|
||||
const criticalDamage = await getCritDamageBonus(part.formula);
|
||||
if (criticalDamage) {
|
||||
damage.formula = `${damage.formula} + ${criticalDamage}`;
|
||||
part.formula = `${part.formula} + ${criticalDamage}`;
|
||||
part.modifierTotal = part.modifierTotal + criticalDamage;
|
||||
part.total += criticalDamage;
|
||||
part.roll = new Roll(part.formula);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
systemData.damage[key].formula = `${systemData.damage[key].formula} + ${damage.formula}`;
|
||||
systemData.damage[key].total += damageTotal;
|
||||
systemData.damage[key].parts = [...systemData.damage[key].parts, ...updatedDamageParts];
|
||||
} else {
|
||||
systemData.damage[key] = damage;
|
||||
systemData.damage[key] = { ...damage, total: damageTotal, parts: updatedDamageParts };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
|
||||
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: 'dualityRoll',
|
||||
|
|
@ -233,14 +263,16 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
||||
for (let memberId of Object.keys(this.data.members)) {
|
||||
const resourceUpdates = [];
|
||||
if (systemData.roll.isCritical || systemData.roll.result.duality === 1) {
|
||||
const value =
|
||||
memberId !== this.data.initiator.id
|
||||
? 1
|
||||
: this.data.initiator.cost
|
||||
? 1 - this.data.initiator.cost
|
||||
: 1;
|
||||
const rollGivesHope = systemData.roll.isCritical || systemData.roll.result.duality === 1;
|
||||
if (memberId === this.data.initiator.id) {
|
||||
const value = this.data.initiator.cost
|
||||
? rollGivesHope
|
||||
? 1 - this.data.initiator.cost
|
||||
: -this.data.initiator.cost
|
||||
: 1;
|
||||
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.result.duality === -1) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (!this.actor) return context;
|
||||
|
||||
context.partyOnCanvas =
|
||||
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)
|
||||
? false
|
||||
: context.canToggleCombat;
|
||||
|
||||
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, 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;
|
||||
}, {});
|
||||
|
|
@ -55,14 +60,33 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}
|
||||
|
||||
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
|
||||
.filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||
.filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
|
||||
.map(t => t.document);
|
||||
if (!this.object.controlled) tokens.push(this.document);
|
||||
if (!this.object.controlled && this.document.actor) tokens.push(this.document);
|
||||
|
||||
try {
|
||||
if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens);
|
||||
else await TokenDocument.implementation.createCombatants(tokens);
|
||||
if (this.document.inCombat) {
|
||||
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) {
|
||||
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
|
||||
const activeEffects = this.actor?.effects || [];
|
||||
const activeEffects = this.actor?.getActiveEffects() || [];
|
||||
for (const effect of activeEffects) {
|
||||
for (const statusId of effect.statuses) {
|
||||
const status = choices[statusId];
|
||||
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 !== effect.id) continue;
|
||||
}
|
||||
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;
|
||||
case 'experience':
|
||||
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 experience = Object.keys(this.actor.system.experiences).find(
|
||||
x => x === data
|
||||
);
|
||||
return this.actor.system.experiences[experience]?.name ?? '';
|
||||
const experience = Object.keys(allExperiences).find(x => x === data);
|
||||
return allExperiences[experience]?.name ?? '';
|
||||
});
|
||||
advancement[choiceKey].push({ data: data, value: checkbox.value });
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -357,11 +357,23 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
|
||||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||
if (experienceIncreaseTagify) {
|
||||
const allExperiences = {
|
||||
...this.actor.system.experiences,
|
||||
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||
for (const key of Object.keys(level.achievements.experiences)) {
|
||||
acc[key] = level.achievements.experiences[key];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
tagifyElement(
|
||||
experienceIncreaseTagify,
|
||||
Object.keys(this.actor.system.experiences).reduce((acc, id) => {
|
||||
const experience = this.actor.system.experiences[id];
|
||||
acc.push({ id: id, label: experience.name });
|
||||
Object.keys(allExperiences).reduce((acc, id) => {
|
||||
const experience = allExperiences[id];
|
||||
if (experience.name) {
|
||||
acc.push({ id: id, label: experience.name });
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []),
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
|||
context.diceSoNiceSystems = Object.fromEntries(
|
||||
[...game.dice3d.DiceFactory.systems].map(([k, v]) => [k, v.name])
|
||||
);
|
||||
context.diceSoNiceFonts = game.dice3d.exports.Utils.prepareFontList();
|
||||
|
||||
foundry.utils.mergeObject(
|
||||
context.dsnTabs,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
icon: 'fa-solid fa-gears'
|
||||
},
|
||||
actions: {
|
||||
editCurrencyIcon: this.changeCurrencyIcon,
|
||||
addItem: this.addItem,
|
||||
editItem: this.editItem,
|
||||
removeItem: this.removeItem,
|
||||
|
|
@ -43,6 +44,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
deleteAdversaryType: this.deleteAdversaryType,
|
||||
selectAdversaryType: this.selectAdversaryType,
|
||||
save: this.save,
|
||||
resetTokenSizes: this.resetTokenSizes,
|
||||
reset: this.reset
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
|
|
@ -115,6 +117,45 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
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) {
|
||||
const { type } = target.dataset;
|
||||
if (['shortRest', 'longRest'].includes(type)) {
|
||||
|
|
@ -384,6 +425,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
this.close();
|
||||
}
|
||||
|
||||
static async resetTokenSizes() {
|
||||
await this.settings.updateSource({
|
||||
tokenSizes: this.settings.schema.fields.tokenSizes.initial
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async reset() {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export { default as ActionConfig } from './action-config.mjs';
|
||||
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||
export { default as CharacterSettings } from './character-settings.mjs';
|
||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||
export { default as 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';
|
||||
|
||||
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}`;
|
||||
}
|
||||
import DHActionBaseConfig from './action-base-config.mjs';
|
||||
|
||||
export default class DHActionConfig extends DHActionBaseConfig {
|
||||
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' },
|
||||
...DHActionBaseConfig.DEFAULT_OPTIONS,
|
||||
actions: {
|
||||
toggleSection: this.toggleSection,
|
||||
...DHActionBaseConfig.DEFAULT_OPTIONS.actions,
|
||||
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
|
||||
editEffect: this.editEffect
|
||||
}
|
||||
};
|
||||
|
||||
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(false);
|
||||
context.openSection = this.openSection;
|
||||
context.tabs = this._getTabs(this.constructor.TABS);
|
||||
context.config = CONFIG.DH;
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
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.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;
|
||||
}
|
||||
|
||||
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) {
|
||||
static async addEffect(_event) {
|
||||
if (!this.action.effects) return;
|
||||
const effectData = this._addEffectData.bind(this)(),
|
||||
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
|
||||
data = this.action.toObject();
|
||||
const effectData = this._addEffectData.bind(this)();
|
||||
const data = this.action.toObject();
|
||||
|
||||
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], {
|
||||
render: false
|
||||
});
|
||||
data.effects.push({ _id: created._id });
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
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) {
|
||||
if (!this.action.effects) return;
|
||||
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)) {
|
||||
const model = game.system.api.models.actors[key];
|
||||
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 choices = CONFIG.Token.documentClass
|
||||
.getTrackedAttributeChoices(attributes, model)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,19 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
}
|
||||
};
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = context.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
|
@ -98,16 +111,16 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
|
||||
async _onDrop(event) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const itemData = item.toObject();
|
||||
delete itemData._id;
|
||||
|
||||
|
||||
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -109,9 +122,9 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
|
||||
async _onDrop(event) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
if (data.fromInternal) return;
|
||||
|
||||
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')) {
|
||||
const target = event.target.closest('.category-container');
|
||||
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 */
|
||||
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: DhPrototypeTokenConfig.TURN_MARKER_MODES,
|
||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||
};
|
||||
static DEFAULT_OPTIONS = {
|
||||
...super.DEFAULT_OPTIONS,
|
||||
form: { handler: DhPrototypeTokenConfig.#onSubmit }
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config'],
|
||||
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'],
|
||||
tag: 'form',
|
||||
position: {
|
||||
width: 560
|
||||
|
|
@ -131,6 +131,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
if (partId in context.tabs) context.tab = context.tabs[partId];
|
||||
switch (partId) {
|
||||
case 'details':
|
||||
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
|
||||
context.isActorEffect = false;
|
||||
context.isItemEffect = true;
|
||||
const useGeneric = game.settings.get(
|
||||
|
|
@ -138,10 +139,13 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
).showGenericStatusEffects;
|
||||
if (!useGeneric) {
|
||||
context.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
||||
value: status.id,
|
||||
label: game.i18n.localize(status.name)
|
||||
}));
|
||||
context.statuses = [
|
||||
...context.statuses,
|
||||
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
|
||||
value: status.id,
|
||||
label: game.i18n.localize(status.name)
|
||||
}))
|
||||
];
|
||||
}
|
||||
break;
|
||||
case 'changes':
|
||||
|
|
@ -157,7 +161,7 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
return context;
|
||||
}
|
||||
|
||||
static async #onSubmit(event, form, formData) {
|
||||
static async #onSubmit(_event, _form, formData) {
|
||||
this.data = foundry.utils.expandObject(formData.object);
|
||||
this.close();
|
||||
}
|
||||
|
|
@ -193,11 +197,11 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #addChange() {
|
||||
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||
const changes = Object.values(submitData.changes ?? {});
|
||||
changes.push({});
|
||||
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object);
|
||||
const updatedChanges = Object.values(changes ?? {});
|
||||
updatedChanges.push({});
|
||||
|
||||
this.effect.changes = changes;
|
||||
this.effect = { ...rest, changes: updatedChanges };
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -208,12 +212,12 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi
|
|||
*/
|
||||
static async #deleteChange(event) {
|
||||
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 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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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;
|
||||
|
||||
|
|
@ -102,6 +102,8 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
return (
|
||||
(await foundry.applications.api.DialogV2.input({
|
||||
window: { title: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectType') },
|
||||
position: { width: 300 },
|
||||
classes: ['daggerheart', 'dh-style'],
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
||||
{ types: CONFIG.DH.ACTIONS.actionTypes }
|
||||
|
|
@ -158,16 +160,55 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
this.render();
|
||||
} else {
|
||||
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 });
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.render();
|
||||
return updatedEffects;
|
||||
}).render(true);
|
||||
}
|
||||
}
|
||||
|
||||
static async removeItem(_, target) {
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||
const { type, id } = target.dataset;
|
||||
if (type === 'effect') {
|
||||
const move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
for (const action of move.actions) {
|
||||
const remainingEffects = action.effects.filter(x => x._id !== id);
|
||||
if (action.effects.length !== remainingEffects.length) {
|
||||
await action.update({
|
||||
effects: remainingEffects.map(x => {
|
||||
const { _id, ...rest } = x;
|
||||
return { ...rest, _id: _id };
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
await this.settings.updateSource({
|
||||
[this.movePath]: {
|
||||
effects: move.effects.filter(x => x.id !== id),
|
||||
actions: move.actions
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await this.settings.updateSource({ [`${this.actionsPath}.-=${target.dataset.id}`]: null });
|
||||
}
|
||||
|
||||
this.move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
this.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 {
|
||||
/** @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: DhTokenConfig.TURN_MARKER_MODES,
|
||||
turnMarkerAnimations: CONFIG.Combat.settings.turnMarkerAnimations
|
||||
};
|
||||
import DHTokenConfigMixin from './token-config-mixin.mjs';
|
||||
import { getActorSizeFromForm } from './token-config-mixin.mjs';
|
||||
|
||||
export default class DhTokenConfig extends DHTokenConfigMixin(foundry.applications.sheets.TokenConfig) {
|
||||
async _processSubmitData(event, form, submitData, options) {
|
||||
const changedTokenSizeValue = getActorSizeFromForm(this.element, this.actor);
|
||||
if (changedTokenSizeValue) this.token.actor.update({ 'system.size': changedTokenSizeValue });
|
||||
|
||||
super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
action: 'editAttribution'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
@ -87,6 +93,13 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
context.resources.stress.emptyPips =
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -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 */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils
|
|||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
export default class CharacterSheet extends DHBaseActorSheet {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
|
|
@ -31,8 +30,10 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
toggleEquipItem: CharacterSheet.#toggleEquipItem,
|
||||
toggleResourceDice: CharacterSheet.#toggleResourceDice,
|
||||
handleResourceDice: CharacterSheet.#handleResourceDice,
|
||||
advanceResourceDie: CharacterSheet.#advanceResourceDie,
|
||||
cancelBeastform: CharacterSheet.#cancelBeastform,
|
||||
useDowntime: this.useDowntime
|
||||
useDowntime: this.useDowntime,
|
||||
viewParty: CharacterSheet.#viewParty,
|
||||
},
|
||||
window: {
|
||||
resizable: true,
|
||||
|
|
@ -46,7 +47,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
},
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"]',
|
||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
|
|
@ -139,15 +140,15 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
element.addEventListener('change', this.updateItemResource.bind(this));
|
||||
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
|
||||
htmlElement.querySelectorAll('.armor-marks-input').forEach(element => {
|
||||
element.addEventListener('change', this.updateArmorMarks.bind(this));
|
||||
});
|
||||
|
||||
htmlElement.querySelectorAll('.item-resource.die').forEach(element => {
|
||||
element.addEventListener('contextmenu', this.lowerResourceDie.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
|
@ -210,34 +211,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.resources.stress.emptyPips =
|
||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||
|
||||
context.inventory = { currencies: {} };
|
||||
const { title, ...currencies } = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Homebrew
|
||||
).currency;
|
||||
for (let key in currencies) {
|
||||
context.inventory.currencies[key] = {
|
||||
...currencies[key],
|
||||
field: context.systemFields.gold.fields[key],
|
||||
value: context.source.system.gold[key]
|
||||
};
|
||||
}
|
||||
// context.inventory = {
|
||||
// currency: {
|
||||
// title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
|
||||
// coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
|
||||
// handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
|
||||
// bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
|
||||
// chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
|
||||
// }
|
||||
// };
|
||||
|
||||
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||
|
||||
// if (context.inventory.length === 0) {
|
||||
// context.inventory = Array(1).fill(Array(5).fill([]));
|
||||
// }
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -344,6 +319,40 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
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',
|
||||
icon: 'fa-solid fa-arrow-down',
|
||||
|
|
@ -615,14 +624,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
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) {
|
||||
const armor = this.document.system.armor;
|
||||
if (!armor) return;
|
||||
|
|
@ -709,17 +710,21 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
roll: {
|
||||
trait: button.dataset.attribute
|
||||
},
|
||||
hasRoll: true
|
||||
};
|
||||
const result = await this.document.diceRoll({
|
||||
...config,
|
||||
hasRoll: true,
|
||||
actionType: 'action',
|
||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
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
|
||||
|
|
@ -858,6 +863,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
}
|
||||
|
||||
/** */
|
||||
static #advanceResourceDie(_, target) {
|
||||
this.updateResourceDie(target, true);
|
||||
}
|
||||
|
||||
lowerResourceDie(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.updateResourceDie(event.target, false);
|
||||
}
|
||||
|
||||
async updateResourceDie(target, advance) {
|
||||
const item = await getDocFromElement(target);
|
||||
if (!item) return;
|
||||
|
||||
const advancedValue = item.system.resource.value + (advance ? 1 : -1);
|
||||
await item.update({
|
||||
'system.resource.value': Math.min(advancedValue, Number(item.system.resource.dieFaces.split('d')[1]))
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
@ -867,6 +893,41 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
|
||||
}
|
||||
|
||||
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.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -877,34 +938,18 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _onDragStart(event) {
|
||||
const item = await getDocFromElement(event.target);
|
||||
|
||||
const dragData = {
|
||||
type: item.documentName,
|
||||
uuid: item.uuid
|
||||
};
|
||||
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||
|
||||
const inventoryItem = event.currentTarget.closest('.inventory-item');
|
||||
if (inventoryItem) {
|
||||
event.dataTransfer.setDragImage(inventoryItem.querySelector('img'), 60, 0);
|
||||
}
|
||||
super._onDragStart(event);
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
// Prevent event bubbling to avoid duplicate handling
|
||||
event.preventDefault();
|
||||
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;
|
||||
async _onDropItem(event, item) {
|
||||
if (this.document.uuid === item.parent?.uuid) {
|
||||
return super._onDropItem(event, item);
|
||||
}
|
||||
|
||||
if (item.type === 'beastform') {
|
||||
|
|
@ -914,20 +959,27 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
);
|
||||
}
|
||||
|
||||
const itemData = item.toObject();
|
||||
const data = await game.system.api.data.items.DHBeastform.getWildcardImage(this.document, itemData);
|
||||
if (data) {
|
||||
if (!data.selectedImage) return;
|
||||
else {
|
||||
if (data.usesDynamicToken) itemData.system.tokenRingImg = data.selectedImage;
|
||||
else itemData.system.tokenImg = data.selectedImage;
|
||||
}
|
||||
if (!data?.selectedImage) {
|
||||
return;
|
||||
} else if (data) {
|
||||
if (data.usesDynamicToken) itemData.system.tokenRingImg = 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);
|
||||
const createdItem = await this._onDropItemCreate(itemData);
|
||||
// If this is a type that gets deleted, delete it first (but still defer to super)
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,12 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
toggleResourceDice: DhpEnvironment.#toggleResourceDice,
|
||||
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 */
|
||||
|
|
@ -37,11 +42,11 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
header: { template: 'systems/daggerheart/templates/sheets/actors/environment/header.hbs' },
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/environment/features.hbs',
|
||||
scrollable: ['feature-section']
|
||||
scrollable: ['.feature-section']
|
||||
},
|
||||
potentialAdversaries: {
|
||||
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' }
|
||||
};
|
||||
|
|
@ -74,6 +79,9 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
|
||||
break;
|
||||
case 'features':
|
||||
await this._prepareFeaturesContext(context, options);
|
||||
break;
|
||||
case 'notes':
|
||||
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.
|
||||
* @param {ApplicationRenderContext} context
|
||||
|
|
@ -130,12 +154,13 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
const item = event.currentTarget.closest('.inventory-item');
|
||||
|
||||
const item = event.currentTarget.closest('.inventory-item[data-type=adversary]');
|
||||
if (item) {
|
||||
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
||||
event.dataTransfer.setDragImage(item, 60, 0);
|
||||
} else {
|
||||
return super._onDragStart(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
|||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||
import DhpActor from '../../../documents/actor.mjs';
|
||||
import DHItem from '../../../documents/item.mjs';
|
||||
import DhParty from '../../../data/actor/party.mjs';
|
||||
|
||||
export default class Party extends DHBaseActorSheet {
|
||||
constructor(options) {
|
||||
|
|
@ -21,7 +20,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
classes: ['party'],
|
||||
position: {
|
||||
width: 550,
|
||||
height: 900,
|
||||
height: 900
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
|
|
@ -41,7 +40,7 @@ export default class Party extends DHBaseActorSheet {
|
|||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||
refreshActors: DaggerheartMenu.refreshActors
|
||||
},
|
||||
dragDrop: [{ dragSelector: '.actors-section .inventory-item', dropSelector: null }]
|
||||
dragDrop: [{ dragSelector: '[data-item-id]', dropSelector: null }]
|
||||
};
|
||||
|
||||
/**@override */
|
||||
|
|
@ -94,25 +93,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
/* Prepare Context */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.inventory = { currencies: {} };
|
||||
const { title, ...currencies } = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Homebrew
|
||||
).currency;
|
||||
for (let key in currencies) {
|
||||
context.inventory.currencies[key] = {
|
||||
...currencies[key],
|
||||
field: context.systemFields.gold.fields[key],
|
||||
value: context.source.system.gold[key]
|
||||
};
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
|
|
@ -440,24 +420,8 @@ export default class Party extends DHBaseActorSheet {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
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();
|
||||
|
||||
async _onDropActor(event, document) {
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const document = await foundry.utils.fromUuid(data.uuid);
|
||||
|
||||
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
|
||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||
if (currentMembers.includes(data.uuid)) {
|
||||
|
|
@ -465,11 +429,11 @@ export default class Party extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
|
||||
} else if (document instanceof DHItem) {
|
||||
this.document.createEmbeddedDocuments('Item', [document.toObject()]);
|
||||
} else {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static async #deletePartyMember(event, target) {
|
||||
|
|
|
|||
|
|
@ -178,6 +178,79 @@ export default function DHApplicationMixin(Base) {
|
|||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
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 */
|
||||
|
|
@ -322,10 +395,11 @@ export default function DHApplicationMixin(Base) {
|
|||
_onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
if (data.fromInternal === this.document.uuid) return;
|
||||
|
||||
if (data.type === 'ActiveEffect') {
|
||||
if (data.type === 'ActiveEffect' && data.fromInternal !== this.document.uuid) {
|
||||
this.document.createEmbeddedDocuments('ActiveEffect', [data.data]);
|
||||
} else {
|
||||
// Fallback to super, but note that item sheets do not have this function
|
||||
return super._onDrop?.(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -662,6 +736,9 @@ export default function DHApplicationMixin(Base) {
|
|||
};
|
||||
if (inVault) data['system.inVault'] = true;
|
||||
if (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 });
|
||||
if (parentIsItem && type === 'feature') {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getDocFromElement, itemIsIdentical } from '../../../helpers/utils.mjs';
|
||||
import DHBaseActorSettings from './actor-setting.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)
|
||||
.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;
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +138,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
_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 => {
|
||||
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 });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Listener Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async updateItemQuantity(event) {
|
||||
const item = await getDocFromElement(event.currentTarget);
|
||||
await item?.update({ 'system.quantity': event.currentTarget.value });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -217,13 +257,98 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
/* 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.
|
||||
* @param {DragEvent} event - The drag 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) {
|
||||
const attackData = {
|
||||
type: 'Attack',
|
||||
|
|
@ -233,8 +358,20 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
};
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
|
||||
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
|
||||
} else if (this.document.type !== 'environment') {
|
||||
super._onDragStart(event);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
removeResource: DHBaseItemSheet.#removeResource
|
||||
},
|
||||
dragDrop: [
|
||||
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
|
||||
{ dragSelector: null, dropSelector: '.drop-section' },
|
||||
{ dragSelector: '.feature-item', dropSelector: null },
|
||||
{ dragSelector: '.action-item', dropSelector: null }
|
||||
{ dragSelector: '.inventory-item', dropSelector: null }
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
|
|
@ -199,18 +199,32 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
static async #deleteFeature(_, element) {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
const feature = await getDocFromElement(target);
|
||||
|
||||
if (!feature) {
|
||||
await this.document.update({
|
||||
'system.features': this.document.system.features
|
||||
.filter(x => x.item)
|
||||
.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({
|
||||
'system.features': this.document.system.features
|
||||
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
|
||||
.map(x => ({ ...x, item: x.item.uuid }))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,37 +256,30 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
* @param {DragEvent} event - The drag event
|
||||
*/
|
||||
async _onDragStart(event) {
|
||||
/* Can prolly be improved a lot, but I don't wanna >_< */
|
||||
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 feature = this.document.system.features.find(x => x?.id === featureItem.id);
|
||||
if (!feature) {
|
||||
const dragItem = await foundry.utils.fromUuid(dragItemData.dataset.itemUuid);
|
||||
if (dragItem) {
|
||||
if (!dragItem) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
|
||||
return;
|
||||
}
|
||||
|
||||
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||
} else {
|
||||
const actionItem = event.currentTarget.closest('.action-item');
|
||||
if (actionItem) {
|
||||
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
|
||||
let dragData = {};
|
||||
if (dragItemData.dataset.type === 'effect')
|
||||
dragData = {
|
||||
type: 'ActiveEffect',
|
||||
fromInternal: this.document.uuid,
|
||||
data: { ...dragItem, uuid: dragItem.uuid, id: dragItem.id }
|
||||
};
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
|
||||
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
|
||||
} else {
|
||||
super._onDragStart(event);
|
||||
}
|
||||
else dragData = { type: 'Item', uuid: dragItem.uuid, id: dragItem.id, fromInternal: this.document.id };
|
||||
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
|
||||
event.dataTransfer.setDragImage(dragItemData.querySelector('img'), 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +291,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
super._onDrop(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');
|
||||
let item = await fromUuid(data.uuid);
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
|
|||
name: context.document.system.advantageOn[key].value
|
||||
}))
|
||||
);
|
||||
context.dimensionsDisabled = context.document.system.tokenSize.size !== 'custom';
|
||||
break;
|
||||
case 'effects':
|
||||
context.effects.actives = context.effects.actives.map(effect => {
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
const item = data.data ?? (await fromUuid(data.uuid));
|
||||
const itemType = data.data ? data.type : item.type;
|
||||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (itemType === 'subclass') {
|
||||
if (item.system.linkedClass) {
|
||||
|
|
|
|||
|
|
@ -31,4 +31,11 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
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 DhActorDirectory } from './tabs/actorDirectory.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 { AbstractSidebarTab } = foundry.applications.sidebar;
|
||||
/**
|
||||
|
|
@ -58,7 +60,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
|
||||
const updates = {};
|
||||
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])
|
||||
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
|
||||
refreshedActors[actor.id].refreshed.add(
|
||||
|
|
@ -76,10 +78,10 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
|
||||
};
|
||||
}
|
||||
if (item.system.metadata.hasActions) {
|
||||
if (item.system.metadata?.hasActions) {
|
||||
const refreshTypes = new Set();
|
||||
const actions = item.system.actions.filter(action => {
|
||||
if (types.includes(action.uses.recovery)) {
|
||||
if (refreshIsAllowed(types, action.uses.recovery)) {
|
||||
refreshTypes.add(action.uses.recovery);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export { default as CountdownEdit } from './countdownEdit.mjs';
|
|||
export { default as DhCountdowns } from './countdowns.mjs';
|
||||
export { default as DhChatLog } from './chatLog.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 DhHotbar } from './hotbar.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 =>
|
||||
element.addEventListener('click', event => this.onRollSimple(event, data.message))
|
||||
element.addEventListener('click', event => this.onRollSimple(event, message))
|
||||
);
|
||||
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 =>
|
||||
element.addEventListener('click', event => this.actionUseButton(event, data.message))
|
||||
element.addEventListener('click', event => this.actionUseButton(event, message))
|
||||
);
|
||||
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 =>
|
||||
element.addEventListener('click', event => this.groupRollButton(event, data.message))
|
||||
element.addEventListener('click', event => this.groupRollButton(event, message))
|
||||
);
|
||||
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 =>
|
||||
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 =>
|
||||
element.addEventListener('click', this.groupRollExpandSection)
|
||||
|
|
@ -133,7 +134,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
|
||||
async actionUseButton(event, message) {
|
||||
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 cls = game.system.api.models.actions.actionsTypes[actionType.type];
|
||||
const action = new cls(
|
||||
|
|
@ -145,7 +148,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
type: CONFIG.DH.ITEM.originItemType.restMove,
|
||||
itemPath: movePath,
|
||||
actionIndex: actionIndex
|
||||
}
|
||||
},
|
||||
targetUuid: targetUuid
|
||||
},
|
||||
{ parent: parent.system }
|
||||
);
|
||||
|
|
@ -232,7 +236,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
hasRoll: true,
|
||||
skips: {
|
||||
createMessage: true,
|
||||
resources: !isLeader
|
||||
resources: !isLeader,
|
||||
updateCountdowns: !isLeader
|
||||
}
|
||||
};
|
||||
const result = await actor.diceRoll({
|
||||
|
|
@ -244,7 +249,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
});
|
||||
|
||||
if (!result) return;
|
||||
await game.system.api.fields.ActionFields.CostField.execute.call({ actor }, result);
|
||||
|
||||
const newMessageData = foundry.utils.deepClone(message.system);
|
||||
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
||||
|
|
@ -291,7 +295,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
},
|
||||
hasRoll: true,
|
||||
skips: {
|
||||
createMessage: true
|
||||
createMessage: true,
|
||||
updateCountdowns: true
|
||||
}
|
||||
};
|
||||
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 {
|
||||
static DEFAULT_OPTIONS = {
|
||||
actions: {
|
||||
requestSpotlight: this.requestSpotlight,
|
||||
toggleSpotlight: this.toggleSpotlight,
|
||||
setActionTokens: this.setActionTokens,
|
||||
openCountdowns: this.openCountdowns
|
||||
setActionTokens: this.setActionTokens
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -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) {
|
||||
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, {
|
||||
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 characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||
const spotlightQueueEnabled = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.SpotlightRequestQueue
|
||||
);
|
||||
|
||||
const spotlightRequests = characters
|
||||
?.filter(x => !x.isNPC)
|
||||
?.filter(x => !x.isNPC && spotlightQueueEnabled)
|
||||
.filter(x => x.system.spotlight.requestOrderIndex > 0)
|
||||
.sort((a, b) => {
|
||||
const valueA = a.system.spotlight.requestOrderIndex;
|
||||
const valueB = b.system.spotlight.requestOrderIndex;
|
||||
|
||||
return valueA - valueB;
|
||||
});
|
||||
|
||||
Object.assign(context, {
|
||||
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
||||
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
|
||||
});
|
||||
}
|
||||
|
|
@ -99,6 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
resource,
|
||||
active: index === combat.turn,
|
||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
type: combatant.actor?.system?.type,
|
||||
img: await this._getCombatantThumbnail(combatant)
|
||||
};
|
||||
|
||||
|
|
@ -136,7 +165,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
|
||||
if (this.viewed.turn !== toggleTurn) {
|
||||
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;
|
||||
if (autoPoints) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -26,6 +27,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
toggleCountdownEdit: CountdownEdit.#toggleCountdownEdit,
|
||||
editCountdownImage: CountdownEdit.#editCountdownImage,
|
||||
editCountdownOwnership: CountdownEdit.#editCountdownOwnership,
|
||||
randomiseCountdownStart: CountdownEdit.#randomiseCountdownStart,
|
||||
removeCountdown: CountdownEdit.#removeCountdown
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true }
|
||||
|
|
@ -44,18 +46,32 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
context.ownershipDefaultOptions = CONFIG.DH.GENERAL.basicOwnershiplevels;
|
||||
context.defaultOwnership = this.data.defaultOwnership;
|
||||
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.countdowns = Object.keys(this.data.countdowns).reduce((acc, 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] = {
|
||||
...countdown,
|
||||
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].name),
|
||||
typeName: game.i18n.localize(CONFIG.DH.GENERAL.countdownBaseTypes[countdown.type].label),
|
||||
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;
|
||||
|
|
@ -111,18 +127,26 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
// Sync current and max if max is changing and they were equal before
|
||||
for (const [id, countdown] of Object.entries(settingsData.countdowns ?? {})) {
|
||||
const existing = this.data.countdowns[id];
|
||||
const wasEqual = existing && existing.progress.current === existing.progress.max;
|
||||
if (wasEqual && countdown.progress.max !== existing.progress.max) {
|
||||
countdown.progress.current = countdown.progress.max;
|
||||
} else {
|
||||
countdown.progress.current = Math.min(countdown.progress.current, countdown.progress.max);
|
||||
}
|
||||
countdown.progress.current = this.getMatchingCurrentValue(
|
||||
existing,
|
||||
countdown.progress.start,
|
||||
countdown.progress.current
|
||||
);
|
||||
}
|
||||
|
||||
this.hideNewCountdowns = hideNewCountdowns;
|
||||
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) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data),
|
||||
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 });
|
||||
}
|
||||
|
||||
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) {
|
||||
const { countdownId } = button.dataset;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -13,7 +14,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this.sidebarCollapsed = true;
|
||||
this.setupHooks();
|
||||
}
|
||||
|
||||
|
|
@ -33,6 +33,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
actions: {
|
||||
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||
editCountdowns: DhCountdowns.#editCountdowns,
|
||||
loopCountdown: DhCountdowns.#loopCountdown,
|
||||
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||
},
|
||||
|
|
@ -81,33 +82,25 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return frame;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _onFirstRender(context, options) {
|
||||
await super._onFirstRender(context, options);
|
||||
|
||||
this.toggleCollapsedPosition(undefined, !ui.sidebar.expanded);
|
||||
}
|
||||
|
||||
/** Returns countdown data filtered by ownership */
|
||||
#getCountdowns() {
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
||||
key,
|
||||
countdown,
|
||||
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
|
||||
const values = Object.entries(setting.countdowns).map(([key, countdown]) => ({
|
||||
key,
|
||||
countdown,
|
||||
ownership: DhCountdowns.#getPlayerOwnership(game.user, setting, countdown)
|
||||
}));
|
||||
return values.filter((v) => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
||||
return values.filter(v => v.ownership !== CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.isGM = game.user.isGM;
|
||||
context.sidebarCollapsed = this.sidebarCollapsed;
|
||||
|
||||
context.iconOnly =
|
||||
game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode) ===
|
||||
CONFIG.DH.GENERAL.countdownAppMode.iconOnly;
|
||||
|
||||
const setting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
context.countdowns = this.#getCountdowns().reduce((acc, { key, countdown, ownership }) => {
|
||||
const playersWithAccess = game.users.reduce((acc, user) => {
|
||||
|
|
@ -118,11 +111,27 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return acc;
|
||||
}, []);
|
||||
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] = {
|
||||
...countdown,
|
||||
editable: game.user.isGM || ownership === CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER,
|
||||
playerAccess: playersWithAccess.length !== nonGmPlayers.length ? playersWithAccess : [],
|
||||
noPlayerAccess: nonGmPlayers.length && playersWithAccess.length === 0
|
||||
editable: countdownEditable,
|
||||
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;
|
||||
}, {});
|
||||
|
|
@ -137,14 +146,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
: 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 }) => {
|
||||
if (refreshType === RefreshType.Countdown) this.render();
|
||||
};
|
||||
|
|
@ -176,13 +177,46 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
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) {
|
||||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = settings.countdowns[target.id];
|
||||
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);
|
||||
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
|
|
@ -200,7 +234,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
}
|
||||
|
||||
setupHooks() {
|
||||
Hooks.on('collapseSidebar', this.toggleCollapsedPosition.bind());
|
||||
Hooks.on(socketEvent.Refresh, this.cooldownRefresh.bind());
|
||||
}
|
||||
|
||||
|
|
@ -208,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 */
|
||||
if (options.closeKey) return;
|
||||
|
||||
Hooks.off('collapseSidebar', this.toggleCollapsedPosition);
|
||||
Hooks.off(socketEvent.Refresh, this.cooldownRefresh);
|
||||
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 updatedCountdowns = Object.keys(countdownSetting.countdowns).reduce((acc, 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);
|
||||
}
|
||||
|
||||
|
|
@ -225,7 +266,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
}, []);
|
||||
|
||||
const countdownData = countdownSetting.toObject();
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, {
|
||||
const settings = {
|
||||
...countdownData,
|
||||
countdowns: Object.keys(countdownData.countdowns).reduce((acc, key) => {
|
||||
const countdown = foundry.utils.deepClone(countdownData.countdowns[key]);
|
||||
|
|
@ -236,18 +277,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
acc[key] = countdown;
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns,
|
||||
DhCountdowns.gmSetSetting.bind(settings),
|
||||
settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
|
||||
const data = { refreshType: RefreshType.Countdown };
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data
|
||||
});
|
||||
Hooks.callAll(socketEvent.Refresh, data);
|
||||
}
|
||||
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
this.element.hidden = !game.user.isGM && this.#getCountdowns().length === 0;
|
||||
if (options?.force) {
|
||||
document.getElementById('ui-right-column-1')?.appendChild(this.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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 */
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'itemBrowser',
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'loader'],
|
||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser', 'daggerheart-loader'],
|
||||
tag: 'div',
|
||||
window: {
|
||||
frame: true,
|
||||
|
|
@ -277,7 +277,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
toggleLoader(state) {
|
||||
const container = this.element.querySelector('.item-list');
|
||||
return setTimeout(() => {
|
||||
container.classList.toggle('loader', state);
|
||||
container.classList.toggle('daggerheart-loader', state);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,29 +10,7 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
this.effects.overlay = null;
|
||||
|
||||
// Categorize effects
|
||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||
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 activeEffects = this.actor?.getActiveEffects() ?? [];
|
||||
const overlayEffect = activeEffects.findLast(e => e.img && e.getFlag?.('core', 'overlay'));
|
||||
|
||||
// Draw effects
|
||||
|
|
@ -56,6 +34,69 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
|
|||
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 */
|
||||
_drawBar(number, bar, data) {
|
||||
const val = Number(data.value);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ export * as actionConfig from './actionConfig.mjs';
|
|||
export * as actorConfig from './actorConfig.mjs';
|
||||
export * as domainConfig from './domainConfig.mjs';
|
||||
export * as effectConfig from './effectConfig.mjs';
|
||||
export * as encounterConfig from './encounterConfig.mjs';
|
||||
export * as flagsConfig from './flagsConfig.mjs';
|
||||
export * as generalConfig from './generalConfig.mjs';
|
||||
export * as hooksConfig from './hooksConfig.mjs';
|
||||
export * as itemConfig from './itemConfig.mjs';
|
||||
export * as settingsConfig from './settingsConfig.mjs';
|
||||
export * as systemConfig from './system.mjs';
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ export const actionTypes = {
|
|||
attack: {
|
||||
id: 'attack',
|
||||
name: 'DAGGERHEART.ACTIONS.TYPES.attack.name',
|
||||
icon: 'fa-khanda',
|
||||
icon: 'fa-hand-fist',
|
||||
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: {
|
||||
id: 'healing',
|
||||
name: 'DAGGERHEART.ACTIONS.TYPES.healing.name',
|
||||
|
|
|
|||
|
|
@ -108,52 +108,64 @@ export const adversaryTypes = {
|
|||
bruiser: {
|
||||
id: 'bruiser',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.bruiser.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.bruiser.description',
|
||||
bpCost: 4
|
||||
},
|
||||
horde: {
|
||||
id: 'horde',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.horde.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.horde.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.horde.description',
|
||||
bpCost: 2
|
||||
},
|
||||
leader: {
|
||||
id: 'leader',
|
||||
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: {
|
||||
id: 'minion',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.minion.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.minion.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.minion.description',
|
||||
bpCost: 1,
|
||||
partyAmountPerBP: true
|
||||
},
|
||||
ranged: {
|
||||
id: 'ranged',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.ranged.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.ranged.description',
|
||||
bpCost: 2
|
||||
},
|
||||
skulk: {
|
||||
id: 'skulk',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.skulk.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.skulk.description',
|
||||
bpCost: 2
|
||||
},
|
||||
social: {
|
||||
id: 'social',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.social.description',
|
||||
bpCost: 1
|
||||
},
|
||||
solo: {
|
||||
id: 'solo',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.solo.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.solo.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.solo.description',
|
||||
bpCost: 5
|
||||
},
|
||||
standard: {
|
||||
id: 'standard',
|
||||
label: 'DAGGERHEART.CONFIG.AdversaryType.standard.label',
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.standard.description'
|
||||
description: 'DAGGERHEART.ACTORS.Adversary.standard.description',
|
||||
bpCost: 2
|
||||
},
|
||||
support: {
|
||||
id: 'support',
|
||||
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 = {
|
||||
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',
|
||||
countdownMode: 'countdown-mode'
|
||||
};
|
||||
|
||||
export const combatToggle = 'combat-toggle-origin';
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
@ -298,7 +298,7 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
@ -341,7 +341,7 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
@ -407,7 +407,7 @@ export const defaultRestOptions = {
|
|||
actionType: 'action',
|
||||
chatDisplay: false,
|
||||
target: {
|
||||
type: 'self'
|
||||
type: 'friendly'
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
|
|
@ -611,18 +611,30 @@ export const abilityCosts = {
|
|||
resource: itemAbilityCosts.resource
|
||||
};
|
||||
|
||||
export const countdownTypes = {
|
||||
spotlight: {
|
||||
id: 'spotlight',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.spotlight'
|
||||
export const countdownProgressionTypes = {
|
||||
actionRoll: {
|
||||
id: 'actionRoll',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.actionRoll'
|
||||
},
|
||||
characterAttack: {
|
||||
id: 'characterAttack',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.characterAttack'
|
||||
},
|
||||
characterSpotlight: {
|
||||
id: 'characterSpotlight',
|
||||
label: 'DAGGERHEART.CONFIG.CountdownType.characterSpotlight'
|
||||
},
|
||||
custom: {
|
||||
id: '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 = {
|
||||
|
|
@ -673,11 +685,30 @@ export const simpleOwnershiplevels = {
|
|||
export const countdownBaseTypes = {
|
||||
narrative: {
|
||||
id: 'narrative',
|
||||
name: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
|
||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.types.narrative'
|
||||
},
|
||||
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;
|
||||
|
|
@ -5,7 +5,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'damage',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.burning.actions.burn.description',
|
||||
|
|
@ -174,7 +173,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.hopeful.actions.hope.description',
|
||||
|
|
@ -188,7 +186,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.impenetrable.actions.impenetrable.description',
|
||||
|
|
@ -231,7 +228,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.painful.actions.pain.description',
|
||||
|
|
@ -269,7 +265,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.quiet.actions.quiet.description',
|
||||
|
|
@ -306,7 +301,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'attack',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.resilient.actions.resilient.description',
|
||||
|
|
@ -353,7 +347,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.shifting.actions.shift.description',
|
||||
|
|
@ -373,7 +366,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'attack',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.timeslowing.actions.slowTime.description',
|
||||
|
|
@ -401,7 +393,6 @@ export const armorFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.name',
|
||||
description: 'DAGGERHEART.CONFIG.ArmorFeature.truthseeking.actions.truthseeking.description',
|
||||
|
|
@ -444,7 +435,8 @@ export const armorFeatures = {
|
|||
{
|
||||
key: 'system.resistance.magical.reduction',
|
||||
mode: 2,
|
||||
value: '@system.armorScore'
|
||||
value: '@system.armorScore',
|
||||
priority: 21
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -459,7 +451,16 @@ export const allArmorFeatures = () => {
|
|||
...armorFeatures,
|
||||
...Object.keys(homebrewFeatures).reduce((acc, 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;
|
||||
}, {})
|
||||
};
|
||||
|
|
@ -528,7 +529,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.bouncing.actions.bounce.description',
|
||||
|
|
@ -573,7 +573,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.brutal.actions.addDamage.description',
|
||||
|
|
@ -587,7 +586,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.burning.actions.burn.description',
|
||||
|
|
@ -601,7 +599,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.charged.actions.markStress.description',
|
||||
|
|
@ -638,7 +635,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.concussive.actions.attack.description',
|
||||
|
|
@ -679,7 +675,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deadly.actions.extraDamage.description',
|
||||
|
|
@ -693,7 +688,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.deflecting.actions.deflect.description',
|
||||
|
|
@ -716,7 +710,8 @@ export const weaponFeatures = {
|
|||
{
|
||||
key: 'system.evasion',
|
||||
mode: 2,
|
||||
value: '@system.armorScore'
|
||||
value: '@system.armorScore',
|
||||
priority: 21
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -730,7 +725,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'damage',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.destructive.actions.attack.descriptive',
|
||||
|
|
@ -775,7 +769,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.devastating.actions.devastate.description',
|
||||
|
|
@ -826,7 +819,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubledUp.actions.doubleUp.description',
|
||||
|
|
@ -840,7 +832,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.dueling.actions.duel.description',
|
||||
|
|
@ -854,7 +845,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect', // Should prompt a dc 14 reaction save on adversaries
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.eruptive.actions.erupt.description',
|
||||
|
|
@ -868,7 +858,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.grappling.actions.grapple.description',
|
||||
|
|
@ -888,7 +877,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
||||
|
|
@ -920,7 +908,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'healing',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.healing.actions.heal.description',
|
||||
|
|
@ -968,7 +955,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.hooked.actions.hook.description',
|
||||
|
|
@ -982,7 +968,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.hot.actions.hot.description',
|
||||
|
|
@ -996,7 +981,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.invigorating.actions.invigorate.description',
|
||||
|
|
@ -1010,7 +994,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lifestealing.actions.lifesteal.description',
|
||||
|
|
@ -1024,7 +1007,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lockedOn.actions.lockOn.description',
|
||||
|
|
@ -1038,7 +1020,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.long.actions.long.description',
|
||||
|
|
@ -1052,7 +1033,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.lucky.actions.luck.description',
|
||||
|
|
@ -1090,7 +1070,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.painful.actions.pain.description',
|
||||
|
|
@ -1136,7 +1115,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.parry.actions.parry.description',
|
||||
|
|
@ -1150,7 +1128,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.persuasive.actions.persuade.description',
|
||||
|
|
@ -1187,7 +1164,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.pompous.actions.pompous.description',
|
||||
|
|
@ -1231,7 +1207,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.quick.actions.quick.description',
|
||||
|
|
@ -1269,7 +1244,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.reloading.actions.reload.description',
|
||||
|
|
@ -1283,7 +1257,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.retractable.actions.retract.description',
|
||||
|
|
@ -1297,7 +1270,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.returning.actions.return.description',
|
||||
|
|
@ -1311,7 +1283,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.scary.actions.scare.description',
|
||||
|
|
@ -1355,7 +1326,8 @@ export const weaponFeatures = {
|
|||
{
|
||||
key: 'system.bonuses.damage.primaryWeapon.bonus',
|
||||
mode: 2,
|
||||
value: '@system.traits.agility.value'
|
||||
value: '@system.traits.agility.value',
|
||||
priority: 21
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1367,7 +1339,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.sheltering.actions.shelter.description',
|
||||
|
|
@ -1381,7 +1352,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.startling.actions.startle.description',
|
||||
|
|
@ -1401,7 +1371,6 @@ export const weaponFeatures = {
|
|||
actions: [
|
||||
{
|
||||
type: 'effect',
|
||||
actionType: 'action',
|
||||
chatDisplay: true,
|
||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
|
||||
|
|
@ -1414,11 +1383,21 @@ export const weaponFeatures = {
|
|||
export const allWeaponFeatures = () => {
|
||||
const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures
|
||||
.weaponFeatures;
|
||||
|
||||
return {
|
||||
...weaponFeatures,
|
||||
...Object.keys(homebrewFeatures).reduce((acc, 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;
|
||||
}, {})
|
||||
};
|
||||
|
|
@ -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)));
|
||||
};
|
||||
|
||||
export const featureForm = {
|
||||
passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
|
||||
action: 'DAGGERHEART.CONFIG.FeatureForm.action',
|
||||
reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
|
||||
};
|
||||
|
||||
export const featureTypes = {
|
||||
ancestry: {
|
||||
id: 'ancestry',
|
||||
|
|
@ -1496,21 +1481,6 @@ export const featureSubTypes = {
|
|||
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 = {
|
||||
simple: {
|
||||
id: 'simple',
|
||||
|
|
@ -1519,6 +1489,10 @@ export const itemResourceTypes = {
|
|||
diceValue: {
|
||||
id: 'diceValue',
|
||||
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
|
||||
},
|
||||
die: {
|
||||
id: 'die',
|
||||
label: 'DAGGERHEART.CONFIG.ItemResourceType.die'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ export const gameSettings = {
|
|||
LevelTiers: 'LevelTiers',
|
||||
Countdowns: 'Countdowns',
|
||||
LastMigrationVersion: 'LastMigrationVersion',
|
||||
TagTeamRoll: 'TagTeamRoll'
|
||||
TagTeamRoll: 'TagTeamRoll',
|
||||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
import * as GENERAL from './generalConfig.mjs';
|
||||
import * as DOMAIN from './domainConfig.mjs';
|
||||
import * as ENCOUNTER from './encounterConfig.mjs';
|
||||
import * as ACTOR from './actorConfig.mjs';
|
||||
import * as ITEM from './itemConfig.mjs';
|
||||
import * as SETTINGS from './settingsConfig.mjs';
|
||||
import * as EFFECTS from './effectConfig.mjs';
|
||||
import * as ACTIONS from './actionConfig.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: SYSTEM_ID,
|
||||
ENCOUNTER,
|
||||
GENERAL,
|
||||
DOMAIN,
|
||||
ACTOR,
|
||||
|
|
@ -20,5 +23,6 @@ export const SYSTEM = {
|
|||
EFFECTS,
|
||||
ACTIONS,
|
||||
FLAGS,
|
||||
HOOKS,
|
||||
ITEMBROWSER
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export { default as DhCombat } from './combat.mjs';
|
|||
export { default as DhCombatant } from './combatant.mjs';
|
||||
export { default as DhTagTeamRoll } from './tagTeamRoll.mjs';
|
||||
|
||||
export * as countdowns from './countdowns.mjs';
|
||||
export * as actions from './action/_module.mjs';
|
||||
export * as activeEffects from './activeEffect/_module.mjs';
|
||||
export * as actors from './actor/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import AttackAction from './attackAction.mjs';
|
||||
import BaseAction from './baseAction.mjs';
|
||||
import BeastformAction from './beastformAction.mjs';
|
||||
import CountdownAction from './countdownAction.mjs';
|
||||
import DamageAction from './damageAction.mjs';
|
||||
import EffectAction from './effectAction.mjs';
|
||||
import HealingAction from './healingAction.mjs';
|
||||
|
|
@ -10,6 +11,7 @@ import SummonAction from './summonAction.mjs';
|
|||
export const actionsTypes = {
|
||||
base: BaseAction,
|
||||
attack: AttackAction,
|
||||
countdown: CountdownAction,
|
||||
damage: DamageAction,
|
||||
healing: HealingAction,
|
||||
summon: SummonAction,
|
||||
|
|
|
|||
|
|
@ -37,8 +37,10 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
async use(event, options) {
|
||||
const result = await super.use(event, options);
|
||||
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
||||
if (result.message.system.action.roll?.type === 'attack') {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
||||
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
||||
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
||||
baseAction: new fields.BooleanField({ initial: false }),
|
||||
name: new fields.StringField({ initial: undefined }),
|
||||
description: new fields.HTMLField(),
|
||||
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
|
||||
|
|
@ -32,7 +33,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
initial: 'action',
|
||||
nullable: false,
|
||||
required: true
|
||||
})
|
||||
}),
|
||||
targetUuid: new fields.StringField({ initial: undefined })
|
||||
};
|
||||
|
||||
this.extraSchemas.forEach(s => {
|
||||
|
|
@ -43,6 +45,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
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.
|
||||
*
|
||||
|
|
@ -87,6 +96,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
prepareData() {
|
||||
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
||||
this.img = this.img ?? this.parent?.parent?.img;
|
||||
|
||||
/* Fallback to feature description */
|
||||
this.description = this.description || this.parent?.description;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,12 +163,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @returns {object}
|
||||
*/
|
||||
getRollData(data = {}) {
|
||||
if (!this.actor) return null;
|
||||
const actorData = this.actor.getRollData(false);
|
||||
const actorData = this.actor ? this.actor.getRollData(false) : {};
|
||||
|
||||
// Add Roll results to RollDatas
|
||||
actorData.result = data.roll?.total ?? 1;
|
||||
|
||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
: 1;
|
||||
|
|
@ -185,8 +194,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
async use(event) {
|
||||
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);
|
||||
if (!config) return;
|
||||
|
||||
|
|
@ -200,9 +207,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
|
||||
// Execute the Action Worflow in order based of schema fields
|
||||
await this.executeWorkflow(config);
|
||||
await config.resourceUpdates.updateResources();
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
if (this.chatDisplay) await this.toChat();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
@ -231,8 +241,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
isDirect: !!this.damage?.direct,
|
||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||
data: this.getRollData(),
|
||||
evaluate: this.hasRoll
|
||||
evaluate: this.hasRoll,
|
||||
resourceUpdates: new ResourceUpdateMap(this.actor),
|
||||
targetUuid: this.targetUuid
|
||||
};
|
||||
|
||||
DHBaseAction.applyKeybindings(config);
|
||||
return config;
|
||||
}
|
||||
|
|
@ -314,11 +327,46 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
* @returns {string[]} An array of localized tag strings.
|
||||
*/
|
||||
_getTags() {
|
||||
const tags = [
|
||||
game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`),
|
||||
game.i18n.localize(`DAGGERHEART.CONFIG.ActionType.${this.actionType}`)
|
||||
];
|
||||
const tags = [game.i18n.localize(`DAGGERHEART.ACTIONS.TYPES.${this.type}.name`)];
|
||||
|
||||
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 {
|
||||
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 {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ export default class BeastformEffect extends BaseEffect {
|
|||
base64: false
|
||||
}),
|
||||
tokenSize: new fields.SchemaField({
|
||||
height: new fields.NumberField({ integer: true, nullable: true }),
|
||||
width: new fields.NumberField({ integer: true, nullable: true })
|
||||
height: new fields.NumberField({ integer: false, nullable: true }),
|
||||
width: new fields.NumberField({ integer: false, nullable: true })
|
||||
})
|
||||
}),
|
||||
advantageOn: new fields.ArrayField(new fields.StringField()),
|
||||
|
|
@ -29,6 +29,14 @@ export default class BeastformEffect extends BaseEffect {
|
|||
};
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static migrateData(source) {
|
||||
if (!source.characterTokenData.tokenSize.height) source.characterTokenData.tokenSize.height = 1;
|
||||
if (!source.characterTokenData.tokenSize.width) source.characterTokenData.tokenSize.width = 1;
|
||||
|
||||
return super.migrateData(source);
|
||||
}
|
||||
|
||||
async _onCreate(_data, _options, userId) {
|
||||
if (userId !== game.user.id) return;
|
||||
|
||||
|
|
@ -57,20 +65,38 @@ export default class BeastformEffect extends BaseEffect {
|
|||
}
|
||||
};
|
||||
|
||||
const updateToken = token => ({
|
||||
...baseUpdate,
|
||||
'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 }
|
||||
});
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.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';
|
||||
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
|
|
@ -11,7 +11,8 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
label: 'TYPES.Actor.adversary',
|
||||
type: 'adversary',
|
||||
settingSheet: DHAdversarySettings,
|
||||
hasAttribution: true
|
||||
hasAttribution: true,
|
||||
usesSize: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
integer: true,
|
||||
label: 'DAGGERHEART.GENERAL.hordeHp'
|
||||
}),
|
||||
criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
major: new fields.NumberField({
|
||||
required: true,
|
||||
|
|
@ -57,6 +59,9 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
hitPoints: resourceField(0, 0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
|
||||
stress: resourceField(0, 0, 'DAGGERHEART.GENERAL.stress', true)
|
||||
}),
|
||||
rules: new fields.SchemaField({
|
||||
...commonActorRules()
|
||||
}),
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
name: 'Attack',
|
||||
|
|
@ -121,6 +126,10 @@ export default class DhpAdversary extends BaseDataActor {
|
|||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
|
||||
isItemValid(source) {
|
||||
return source.type === 'feature';
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,24 @@
|
|||
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
|
||||
import DHItem from '../../documents/item.mjs';
|
||||
import { getScrollTextData } from '../../helpers/utils.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
const resistanceField = (resistanceLabel, immunityLabel, reductionLabel) =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
resistance: new foundry.data.fields.BooleanField({
|
||||
new fields.SchemaField({
|
||||
resistance: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: `${resistanceLabel}.label`,
|
||||
hint: `${resistanceLabel}.hint`,
|
||||
isAttributeChoice: true
|
||||
}),
|
||||
immunity: new foundry.data.fields.BooleanField({
|
||||
immunity: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: `${immunityLabel}.label`,
|
||||
hint: `${immunityLabel}.hint`,
|
||||
isAttributeChoice: true
|
||||
}),
|
||||
reduction: new foundry.data.fields.NumberField({
|
||||
reduction: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 0,
|
||||
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
|
||||
* @typedef {Object} ActorDataModelMetadata
|
||||
|
|
@ -41,7 +63,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
settingSheet: null,
|
||||
hasResistances: true,
|
||||
hasAttribution: false,
|
||||
hasLimitedView: true
|
||||
hasLimitedView: true,
|
||||
usesSize: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +75,6 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
const schema = {};
|
||||
|
||||
if (this.metadata.hasAttribution) {
|
||||
|
|
@ -76,6 +98,13 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
'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;
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +135,32 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
|||
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) {
|
||||
const allowed = await super._preUpdate(changes, options, userId);
|
||||
if (allowed === false) return;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.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 { ActionField } from '../fields/actionField.mjs';
|
||||
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
|
||||
|
|
@ -217,40 +217,41 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}),
|
||||
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
||||
rules: new fields.SchemaField({
|
||||
damageReduction: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
required: true,
|
||||
...commonActorRules({
|
||||
damageReduction: {
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false }),
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
||||
}),
|
||||
stressExtra: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
||||
})
|
||||
}),
|
||||
stressDamageReduction: new fields.SchemaField({
|
||||
severe: stressDamageReductionRule(
|
||||
'DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'
|
||||
),
|
||||
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
||||
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
||||
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
||||
}),
|
||||
increasePerArmorMark: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||
}),
|
||||
stressExtra: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
initial: 0,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
|
||||
})
|
||||
}),
|
||||
stressDamageReduction: new fields.SchemaField({
|
||||
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
|
||||
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
|
||||
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor'),
|
||||
any: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.any')
|
||||
}),
|
||||
increasePerArmorMark: new fields.NumberField({
|
||||
integer: true,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
|
||||
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
|
||||
}),
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false }),
|
||||
thresholdImmunities: new fields.SchemaField({
|
||||
minor: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
disabledArmor: new fields.BooleanField({ intial: false })
|
||||
disabledArmor: new fields.BooleanField({ intial: false })
|
||||
}
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
damage: new fields.SchemaField({
|
||||
|
|
@ -426,6 +427,33 @@ export default class DhCharacter extends BaseDataActor {
|
|||
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() {
|
||||
const ancestryFeatures = [],
|
||||
communityFeatures = [],
|
||||
|
|
@ -434,7 +462,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
companionFeatures = [],
|
||||
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) {
|
||||
ancestryFeatures.push(item);
|
||||
} 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) {
|
||||
classFeatures.push(item);
|
||||
} 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) {
|
||||
companionFeatures.push(item);
|
||||
} else if (item.type === 'feature' && !item.system.type) {
|
||||
|
|
@ -669,6 +684,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
async _preDelete() {
|
||||
super._preDelete();
|
||||
|
||||
if (this.companion) {
|
||||
this.companion.updateLevel(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import DhLevelData from '../levelData.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 DHCompanionSettings from '../../applications/sheets-configs/companion-settings.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({
|
||||
initial: {
|
||||
name: 'Attack',
|
||||
|
|
@ -101,6 +108,10 @@ export default class DhCompanion extends BaseDataActor {
|
|||
get proficiency() {
|
||||
return this.partner?.system?.proficiency ?? 1;
|
||||
}
|
||||
|
||||
isItemValid() {
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
|
||||
|
|
|
|||
|
|
@ -51,4 +51,8 @@ export default class DhEnvironment extends BaseDataActor {
|
|||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
|
||||
isItemValid(source) {
|
||||
return source.type === "feature";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ export default class DhParty extends BaseDataActor {
|
|||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
isItemValid(source) {
|
||||
return ['weapon', 'armor', 'consumable', 'loot'].includes(source.type);
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
super.prepareBaseData();
|
||||
|
||||
|
|
@ -36,6 +40,23 @@ export default class DhParty extends BaseDataActor {
|
|||
}
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
/* Clear all partyMembers from tagTeam setting.*/
|
||||
/* Revisit this when tagTeam is improved for many parties */
|
||||
const tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
await tagTeam.updateSource({
|
||||
initiator: this.partyMembers.some(x => x.id === tagTeam.initiator) ? null : tagTeam.initiator,
|
||||
members: Object.keys(tagTeam.members).reduce((acc, key) => {
|
||||
if (this.partyMembers.find(x => x.id === key)) {
|
||||
acc[`-=${key}`] = null;
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
});
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, tagTeam);
|
||||
}
|
||||
|
||||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,40 @@
|
|||
export default class DhCombat extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
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 {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -105,8 +107,8 @@ class DhOldCountdown extends foundry.abstract.DataModel {
|
|||
type: new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
||||
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
}),
|
||||
label: new fields.StringField({
|
||||
|
|
@ -165,16 +167,27 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
|||
initial: 1,
|
||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.current.label'
|
||||
}),
|
||||
max: new fields.NumberField({
|
||||
start: new fields.NumberField({
|
||||
required: true,
|
||||
integer: true,
|
||||
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({
|
||||
required: true,
|
||||
choices: CONFIG.DH.GENERAL.countdownTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
|
||||
choices: CONFIG.DH.GENERAL.countdownProgressionTypes,
|
||||
initial: CONFIG.DH.GENERAL.countdownProgressionTypes.custom.id,
|
||||
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.type.label'
|
||||
})
|
||||
})
|
||||
|
|
@ -198,7 +211,7 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
|||
ownership: ownership,
|
||||
progress: {
|
||||
current: 1,
|
||||
max: 1
|
||||
start: 1
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -217,4 +230,15 @@ export class DhCountdown extends foundry.abstract.DataModel {
|
|||
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 CountdownField } from './countdownField.mjs';
|
||||
export { default as UsesField } from './usesField.mjs';
|
||||
export { default as RangeField } from './rangeField.mjs';
|
||||
export { default as TargetField } from './targetField.mjs';
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export default class BeastformField extends fields.SchemaField {
|
|||
* @returns
|
||||
*/
|
||||
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');
|
||||
if (!beastformEffect) {
|
||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||
|
|
@ -92,6 +92,18 @@ export default class BeastformField extends fields.SchemaField {
|
|||
|
||||
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,15 +47,30 @@ export default class EffectsField extends fields.ArrayField {
|
|||
static async applyEffects(targets) {
|
||||
if (!this.effects?.length || !targets?.length) return;
|
||||
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
let effects = this.effects;
|
||||
const messageTargets = [];
|
||||
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;
|
||||
|
||||
const token = canvas.tokens.get(baseToken.id);
|
||||
const token =
|
||||
canvas.tokens.get(baseToken.id) ?? foundry.utils.fromUuidSync(baseToken.actorId).prototypeToken;
|
||||
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 => {
|
||||
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) {
|
||||
targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
|
||||
await SaveField.rollAllSave.call(this, targets, config.event, message);
|
||||
} else return false;
|
||||
} else return;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -124,29 +124,21 @@ export default class SaveField extends fields.SchemaField {
|
|||
*/
|
||||
static async updateSaveMessage(result, message, targetId) {
|
||||
if (!result) return;
|
||||
const updateMsg = async function (message, targetId, result) {
|
||||
// setTimeout(async () => {
|
||||
const chatMessage = ui.chat.collection.get(message._id),
|
||||
changes = {
|
||||
flags: {
|
||||
[game.system.id]: {
|
||||
reactionRolls: {
|
||||
[targetId]: {
|
||||
result: result.roll.total,
|
||||
success: result.roll.success
|
||||
}
|
||||
|
||||
const chatMessage = ui.chat.collection.get(message._id),
|
||||
changes = {
|
||||
flags: {
|
||||
[game.system.id]: {
|
||||
reactionRolls: {
|
||||
[targetId]: {
|
||||
result: result.roll.total,
|
||||
success: result.roll.success
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
};
|
||||
await chatMessage.update(changes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -25,9 +25,12 @@ export default class TargetField extends fields.SchemaField {
|
|||
config.hasTarget = true;
|
||||
let targets;
|
||||
// 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];
|
||||
else {
|
||||
} else if (config.targetUuid) {
|
||||
const actor = fromUuidSync(config.targetUuid);
|
||||
targets = [actor.token ?? actor.prototypeToken];
|
||||
} else {
|
||||
targets = Array.from(game.user.targets);
|
||||
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
|
||||
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
|
||||
|
|
@ -79,7 +82,7 @@ export default class TargetField extends fields.SchemaField {
|
|||
return {
|
||||
id: token.id,
|
||||
actorId: token.actor.uuid,
|
||||
name: token.actor.name,
|
||||
name: token.name,
|
||||
img: token.actor.img,
|
||||
difficulty: token.actor.system.difficulty,
|
||||
evasion: token.actor.system.evasion,
|
||||
|
|
|
|||
|
|
@ -141,6 +141,12 @@ export function ActionMixin(Base) {
|
|||
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() {
|
||||
return `.Item.${this.item.id}.Action.${this.id}`;
|
||||
}
|
||||
|
|
@ -256,18 +262,27 @@ export function ActionMixin(Base) {
|
|||
async toChat(origin) {
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.ActionType.action'),
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.FeatureForm.action'),
|
||||
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,
|
||||
description: this.description
|
||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||
};
|
||||
|
||||
const speaker = cls.getSpeaker();
|
||||
const msg = {
|
||||
type: 'abilityUse',
|
||||
user: game.user.id,
|
||||
actor: { name: this.actor.name, img: this.actor.img },
|
||||
author: this.author,
|
||||
speaker: cls.getSpeaker(),
|
||||
speaker: {
|
||||
speaker,
|
||||
actor: speaker.actor ?? this.actor
|
||||
},
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
|
||||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ export default class DHBeastform extends BaseDataItem {
|
|||
base64: false
|
||||
}),
|
||||
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 }),
|
||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||
}),
|
||||
|
|
@ -190,9 +196,18 @@ export default class DHBeastform extends BaseDataItem {
|
|||
|
||||
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
|
||||
|
||||
const autoTokenSize =
|
||||
this.tokenSize.size !== 'custom'
|
||||
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes[
|
||||
this.tokenSize.size
|
||||
]
|
||||
: null;
|
||||
const width = autoTokenSize ?? this.tokenSize.width;
|
||||
const height = autoTokenSize ?? this.tokenSize.height;
|
||||
|
||||
const prototypeTokenUpdate = {
|
||||
height: this.tokenSize.height,
|
||||
width: this.tokenSize.width,
|
||||
height,
|
||||
width,
|
||||
texture: {
|
||||
src: this.tokenImg
|
||||
},
|
||||
|
|
@ -202,16 +217,33 @@ export default class DHBeastform extends BaseDataItem {
|
|||
}
|
||||
}
|
||||
};
|
||||
const tokenUpdate = token => {
|
||||
let x = null,
|
||||
y = null;
|
||||
if (token.object?.scene?.grid) {
|
||||
const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
|
||||
token.object.scene.grid,
|
||||
{ x: token.x, y: token.y, elevation: token.elevation },
|
||||
width ?? token.width,
|
||||
height ?? token.height
|
||||
);
|
||||
|
||||
const tokenUpdate = token => ({
|
||||
...prototypeTokenUpdate,
|
||||
flags: {
|
||||
daggerheart: {
|
||||
beastformTokenImg: token.texture.src,
|
||||
beastformSubjectTexture: token.ring.subject.texture
|
||||
}
|
||||
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);
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateDomainCard'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.actor.system.loadoutSlot.available) {
|
||||
data.system.inVault = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,13 @@ export default class DHFeature extends BaseDataItem {
|
|||
initial: null
|
||||
}),
|
||||
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',
|
||||
img: 'icons/skills/melee/blood-slash-foam-red.webp',
|
||||
_id: foundry.utils.randomID(),
|
||||
baseAction: true,
|
||||
chatDisplay: false,
|
||||
systemPath: 'attack',
|
||||
type: 'attack',
|
||||
range: 'melee',
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export default class DhAppearance extends foundry.abstract.DataModel {
|
|||
texture: new StringField({ initial: 'astralsea', required: true, blank: false }),
|
||||
colorset: new StringField({ initial: 'inspired', 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 {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
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({
|
||||
required: true,
|
||||
initial: true,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { defaultRestOptions } from '../../config/generalConfig.mjs';
|
||||
import { ActionsField } from '../fields/actionField.mjs';
|
||||
|
||||
const currencyField = (initial, label) =>
|
||||
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 {
|
||||
|
|
@ -39,16 +40,60 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
traitArray: new fields.ArrayField(new fields.NumberField({ required: true, integer: true }), {
|
||||
initial: () => [2, 1, 1, 0, 0, -1]
|
||||
}),
|
||||
tokenSizes: new fields.SchemaField({
|
||||
tiny: new fields.NumberField({
|
||||
integer: false,
|
||||
initial: 0.5,
|
||||
label: 'DAGGERHEART.CONFIG.TokenSize.tiny'
|
||||
}),
|
||||
small: new fields.NumberField({
|
||||
integer: false,
|
||||
initial: 0.8,
|
||||
label: 'DAGGERHEART.CONFIG.TokenSize.small'
|
||||
}),
|
||||
medium: new fields.NumberField({
|
||||
integer: false,
|
||||
initial: 1,
|
||||
label: 'DAGGERHEART.CONFIG.TokenSize.medium'
|
||||
}),
|
||||
large: new fields.NumberField({
|
||||
integer: false,
|
||||
initial: 2,
|
||||
label: 'DAGGERHEART.CONFIG.TokenSize.large'
|
||||
}),
|
||||
huge: new fields.NumberField({
|
||||
integer: false,
|
||||
initial: 3,
|
||||
label: 'DAGGERHEART.CONFIG.TokenSize.huge'
|
||||
}),
|
||||
gargantuan: new fields.NumberField({
|
||||
integer: false,
|
||||
initial: 4,
|
||||
label: 'DAGGERHEART.CONFIG.TokenSize.gargantuan'
|
||||
})
|
||||
}),
|
||||
currency: new fields.SchemaField({
|
||||
title: new fields.StringField({
|
||||
required: true,
|
||||
initial: 'Gold',
|
||||
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName'
|
||||
}),
|
||||
coins: currencyField('Coins', 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'),
|
||||
handfuls: currencyField('Handfuls', 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'),
|
||||
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'),
|
||||
chests: currencyField('Chests', 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName')
|
||||
coins: currencyField(
|
||||
'Coins',
|
||||
'DAGGERHEART.SETTINGS.Homebrew.currency.coinName',
|
||||
'fa-solid fa-coin-front'
|
||||
),
|
||||
handfuls: currencyField(
|
||||
'Handfuls',
|
||||
'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName',
|
||||
'fa-solid fa-coins'
|
||||
),
|
||||
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName', 'fa-solid fa-sack'),
|
||||
chests: currencyField(
|
||||
'Chests',
|
||||
'DAGGERHEART.SETTINGS.Homebrew.currency.chestName',
|
||||
'fa-solid fa-treasure-chest'
|
||||
)
|
||||
}),
|
||||
restMoves: new fields.SchemaField({
|
||||
longRest: new fields.SchemaField({
|
||||
|
|
@ -139,22 +184,10 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
/** @inheritDoc */
|
||||
_initializeSource(source, options = {}) {
|
||||
source = super._initializeSource(source, options);
|
||||
source.currency.coins = {
|
||||
enabled: source.currency.coins.enabled ?? true,
|
||||
label: source.currency.coins.label || source.currency.coins
|
||||
};
|
||||
source.currency.handfuls = {
|
||||
enabled: source.currency.handfuls.enabled ?? true,
|
||||
label: source.currency.handfuls.label || source.currency.handfuls
|
||||
};
|
||||
source.currency.bags = {
|
||||
enabled: source.currency.bags.enabled ?? true,
|
||||
label: source.currency.bags.label || source.currency.bags
|
||||
};
|
||||
source.currency.chests = {
|
||||
enabled: source.currency.chests.enabled ?? true,
|
||||
label: source.currency.chests.label || source.currency.chests
|
||||
};
|
||||
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'
|
||||
}),
|
||||
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
|
||||
};
|
||||
|
||||
static CRITICAL_TRESHOLD = 20;
|
||||
|
||||
static DefaultDialog = D20RollDialog;
|
||||
|
||||
get title() {
|
||||
|
|
@ -37,7 +35,7 @@ export default class D20Roll extends DHRoll {
|
|||
|
||||
get isCritical() {
|
||||
if (!this.d20._evaluated) return;
|
||||
return this.d20.total >= this.constructor.CRITICAL_TRESHOLD;
|
||||
return this.d20.total >= this.data.system.criticalThreshold;
|
||||
}
|
||||
|
||||
get hasAdvantage() {
|
||||
|
|
@ -100,10 +98,10 @@ export default class D20Roll extends DHRoll {
|
|||
this.options.roll.modifiers = this.applyBaseBonus();
|
||||
|
||||
this.options.experiences?.forEach(m => {
|
||||
if (this.options.data.experiences?.[m])
|
||||
if (this.options.data.system?.experiences?.[m])
|
||||
this.options.roll.modifiers.push({
|
||||
label: this.options.data.experiences[m].name,
|
||||
value: this.options.data.experiences[m].value
|
||||
label: this.options.data.system.experiences[m].name,
|
||||
value: this.options.data.system.experiences[m].value
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export default class DHRoll extends Roll {
|
|||
const actorIdSplit = config.source?.actor?.split('.');
|
||||
if (actorIdSplit) {
|
||||
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) {
|
||||
|
|
@ -115,7 +115,7 @@ export default class DHRoll extends Roll {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -236,52 +236,3 @@ export default class DHRoll extends Roll {
|
|||
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 { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageFaces = 6;
|
||||
|
|
@ -19,7 +20,7 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
get title() {
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
|
||||
const term = parsedRoll.terms[target.dataset.dieIndex];
|
||||
|
|
@ -257,14 +340,20 @@ export default class DualityRoll extends D20Roll {
|
|||
newRoll.extra = newRoll.extra.slice(2);
|
||||
|
||||
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 ?? '' },
|
||||
targets: message.system.targets,
|
||||
tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
|
||||
roll: newRoll,
|
||||
rerolledRoll:
|
||||
newRoll.result.duality !== message.system.roll.result.duality ? message.system.roll : undefined
|
||||
});
|
||||
rerolledRoll: message.system.roll,
|
||||
resourceUpdates: new ResourceUpdateMap(actor)
|
||||
};
|
||||
|
||||
await DualityRoll.addDualityResourceUpdates(config);
|
||||
await config.resourceUpdates.updateResources();
|
||||
|
||||
return { newRoll, parsedRoll };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
export { default as DhpActor } from './actor.mjs';
|
||||
export { default as DHItem } from './item.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 DhChatMessage } from './chatMessage.mjs';
|
||||
export { default as DhScene } from './scene.mjs';
|
||||
export { default as DhToken } from './token.mjs';
|
||||
export { default as DhTooltipManager } from './tooltipManager.mjs';
|
||||
export { default as DhTemplateManager } from './templateManager.mjs';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { itemAbleRollParse } from '../helpers/utils.mjs';
|
||||
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -57,6 +58,27 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
update.img = 'icons/magic/life/heart-cross-blue.webp';
|
||||
}
|
||||
|
||||
const immuneStatuses =
|
||||
data.statuses?.filter(
|
||||
status =>
|
||||
this.parent.system.rules?.conditionImmunities &&
|
||||
this.parent.system.rules.conditionImmunities[status]
|
||||
) ?? [];
|
||||
if (immuneStatuses.length > 0) {
|
||||
update.statuses = data.statuses.filter(x => !immuneStatuses.includes(x));
|
||||
const conditions = CONFIG.DH.GENERAL.conditions();
|
||||
const scrollingTexts = immuneStatuses.map(status => ({
|
||||
text: game.i18n.format('DAGGERHEART.ACTIVEEFFECT.immuneStatusText', {
|
||||
status: game.i18n.localize(conditions[status].name)
|
||||
})
|
||||
}));
|
||||
if (update.statuses.length > 0) {
|
||||
setTimeout(() => scrollingTexts, 500);
|
||||
} else {
|
||||
this.parent.queueScrollText(scrollingTexts);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(update).length > 0) {
|
||||
await this.updateSource(update);
|
||||
}
|
||||
|
|
@ -64,6 +86,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
await super._preCreate(data, options, user);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_onCreate(data, options, userId) {
|
||||
super._onCreate(data, options, userId);
|
||||
|
||||
Hooks.callAll(RefreshType.EffectsDisplay);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_onDelete(data, options, userId) {
|
||||
super._onDelete(data, options, userId);
|
||||
|
||||
Hooks.callAll(RefreshType.EffectsDisplay);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Methods */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -76,7 +112,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
change.value = change.value.replaceAll(/origin\.@/gi, '@');
|
||||
try {
|
||||
const effect = foundry.utils.fromUuidSync(change.effect.origin);
|
||||
const doc = effect.parent?.parent;
|
||||
const doc =
|
||||
effect.parent?.parent instanceof game.system.api.documents.DhpActor
|
||||
? effect.parent
|
||||
: effect.parent.parent;
|
||||
if (doc) parseModel = doc;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
|
@ -155,27 +194,10 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
/* Preventing subclass features from transferring to actor if they do not have the right subclass advancement */
|
||||
if (this.parent?.type === 'feature') {
|
||||
const origSubclassParent = this.parent.system.originItemType === 'subclass';
|
||||
if (origSubclassParent) {
|
||||
const subclass = this.parent.parent.items.find(
|
||||
x =>
|
||||
x.type === 'subclass' &&
|
||||
x.system.isMulticlass === (this.parent.system.identifier === 'multiclass')
|
||||
);
|
||||
|
||||
if (subclass) {
|
||||
const featureState = subclass.system.featureState;
|
||||
|
||||
if (
|
||||
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.specialization &&
|
||||
featureState < 2) ||
|
||||
(this.parent.system.identifier === CONFIG.DH.ITEM.featureSubTypes.mastery && featureState < 3)
|
||||
) {
|
||||
this.transfer = false;
|
||||
}
|
||||
}
|
||||
/* Check for item availability such as in the case of subclass advancement. */
|
||||
if (this.parent?.parent?.system?.isItemAvailable) {
|
||||
if (!this.parent.parent.system.isItemAvailable(this.parent)) {
|
||||
this.transfer = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||
import { LevelOptionType } from '../data/levelTier.mjs';
|
||||
import DHFeature from '../data/item/feature.mjs';
|
||||
import { createScrollText, damageKeyToNumber } from '../helpers/utils.mjs';
|
||||
import { createScrollText, damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
|
||||
import DhCompanionLevelUp from '../applications/levelup/companionLevelup.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
export default class DhpActor extends Actor {
|
||||
parties = new Set();
|
||||
|
|
@ -73,16 +74,27 @@ export default class DhpActor extends Actor {
|
|||
/**@inheritdoc */
|
||||
async _preCreate(data, options, user) {
|
||||
if ((await super._preCreate(data, options, user)) === false) return false;
|
||||
const update = {};
|
||||
|
||||
// Set default token size. Done here as we do not want to set a datamodel default, since that would apply the sizing to third party actor modules that aren't set up with the size system.
|
||||
if (this.system.metadata.usesSize && !data.system?.size) {
|
||||
Object.assign(update, {
|
||||
system: {
|
||||
size: CONFIG.DH.ACTOR.tokenSize.medium.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Configure prototype token settings
|
||||
const prototypeToken = {};
|
||||
if (['character', 'companion', 'party'].includes(this.type))
|
||||
Object.assign(prototypeToken, {
|
||||
sight: { enabled: true },
|
||||
actorLink: true,
|
||||
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
||||
Object.assign(update, {
|
||||
prototypeToken: {
|
||||
sight: { enabled: true },
|
||||
actorLink: true,
|
||||
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
|
||||
}
|
||||
});
|
||||
this.updateSource({ prototypeToken });
|
||||
this.updateSource(update);
|
||||
}
|
||||
|
||||
_onUpdate(changes, options, userId) {
|
||||
|
|
@ -204,7 +216,7 @@ export default class DhpActor extends Actor {
|
|||
|
||||
for (let domainCard of domainCards) {
|
||||
const itemCard = this.items.find(x => x.uuid === domainCard);
|
||||
itemCard.delete();
|
||||
itemCard?.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +349,8 @@ export default class DhpActor extends Actor {
|
|||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...multiclassData,
|
||||
uuid: multiclassItem.uuid,
|
||||
_stats: multiclassItem._stats,
|
||||
system: {
|
||||
...multiclassData.system,
|
||||
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
|
||||
|
|
@ -349,6 +363,8 @@ export default class DhpActor extends Actor {
|
|||
await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...subclassData,
|
||||
uuid: subclassItem.uuid,
|
||||
_stats: subclassItem._stats,
|
||||
system: {
|
||||
...subclassData.system,
|
||||
isMulticlass: true
|
||||
|
|
@ -363,12 +379,15 @@ export default class DhpActor extends Actor {
|
|||
|
||||
for (var domainCard of domainCards) {
|
||||
if (levelupAuto) {
|
||||
const itemData = (await foundry.utils.fromUuid(domainCard.data[0])).toObject();
|
||||
const cardItem = await foundry.utils.fromUuid(domainCard.data[0]);
|
||||
const cardData = cardItem.toObject();
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...itemData,
|
||||
...cardData,
|
||||
uuid: cardItem.uuid,
|
||||
_stats: cardItem._stats,
|
||||
system: {
|
||||
...itemData.system,
|
||||
...cardData.system,
|
||||
inVault: true
|
||||
}
|
||||
}
|
||||
|
|
@ -382,12 +401,15 @@ export default class DhpActor extends Actor {
|
|||
const achievementDomainCards = [];
|
||||
if (levelupAuto) {
|
||||
for (var card of Object.values(level.achievements.domainCards)) {
|
||||
const itemData = (await foundry.utils.fromUuid(card.uuid)).toObject();
|
||||
const cardItem = await foundry.utils.fromUuid(card.uuid);
|
||||
const cardData = cardItem.toObject();
|
||||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...itemData,
|
||||
...cardData,
|
||||
uuid: cardItem.uuid,
|
||||
_stats: cardItem._stats,
|
||||
system: {
|
||||
...itemData.system,
|
||||
...cardData.system,
|
||||
inVault: true
|
||||
}
|
||||
}
|
||||
|
|
@ -467,6 +489,7 @@ export default class DhpActor extends Actor {
|
|||
async diceRoll(config) {
|
||||
config.source = { ...(config.source ?? {}), actor: this.uuid };
|
||||
config.data = this.getRollData();
|
||||
config.resourceUpdates = new ResourceUpdateMap(this);
|
||||
const rollClass = config.roll.lite ? CONFIG.Dice.daggerheart['DHRoll'] : this.rollClass;
|
||||
return await rollClass.build(config);
|
||||
}
|
||||
|
|
@ -516,7 +539,11 @@ export default class DhpActor extends Actor {
|
|||
|
||||
/**@inheritdoc */
|
||||
getRollData() {
|
||||
const rollData = super.getRollData();
|
||||
const rollData = foundry.utils.deepClone(super.getRollData());
|
||||
/* system gets repeated infinately which causes issues when trying to use the data for document creation */
|
||||
delete rollData.system;
|
||||
|
||||
rollData.id = this.id;
|
||||
rollData.name = this.name;
|
||||
rollData.system = this.system.getRollData();
|
||||
rollData.prof = this.system.proficiency ?? 1;
|
||||
|
|
@ -604,6 +631,19 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (this.type === 'adversary') {
|
||||
const reducedSeverity = hpDamage.damageTypes.reduce((value, curr) => {
|
||||
return Math.max(this.system.rules.damageReduction.reduceSeverity[curr], value);
|
||||
}, 0);
|
||||
hpDamage.value = Math.max(hpDamage.value - reducedSeverity, 0);
|
||||
|
||||
if (
|
||||
hpDamage.value &&
|
||||
this.system.rules.damageReduction.thresholdImmunities[getDamageKey(hpDamage.value)]
|
||||
) {
|
||||
hpDamage.value -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updates.forEach(
|
||||
|
|
@ -669,6 +709,10 @@ export default class DhpActor extends Actor {
|
|||
return updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resources are modified asynchronously, so be careful not to update the same resource in
|
||||
* quick succession.
|
||||
*/
|
||||
async modifyResource(resources) {
|
||||
if (!resources?.length) return;
|
||||
|
||||
|
|
@ -751,6 +795,11 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
|
||||
convertDamageToThreshold(damage) {
|
||||
const massiveDamageEnabled = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules)
|
||||
.massiveDamage.enabled;
|
||||
if (massiveDamageEnabled && damage >= this.system.damageThresholds.severe * 2) {
|
||||
return 4;
|
||||
}
|
||||
return damage >= this.system.damageThresholds.severe ? 3 : damage >= this.system.damageThresholds.major ? 2 : 1;
|
||||
}
|
||||
|
||||
|
|
@ -834,4 +883,65 @@ export default class DhpActor extends Actor {
|
|||
if (this.system._getTags) tags.push(...this.system._getTags());
|
||||
return tags;
|
||||
}
|
||||
|
||||
/** Get active effects */
|
||||
getActiveEffects() {
|
||||
const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status]));
|
||||
return this.effects
|
||||
.filter(x => !x.disabled)
|
||||
.reduce((acc, effect) => {
|
||||
acc.push(effect);
|
||||
|
||||
const currentStatusActiveEffects = acc.filter(
|
||||
x => x.statuses.size === 1 && x.name === game.i18n.localize(statusMap.get(x.statuses.first())?.name)
|
||||
);
|
||||
|
||||
for (var status of effect.statuses) {
|
||||
if (!currentStatusActiveEffects.find(x => x.statuses.has(status))) {
|
||||
const statusData = statusMap.get(status);
|
||||
if (statusData) {
|
||||
acc.push({
|
||||
condition: status,
|
||||
appliedBy: game.i18n.localize(effect.name),
|
||||
name: game.i18n.localize(statusData.name),
|
||||
statuses: new Set([status]),
|
||||
img: statusData.icon ?? statusData.img,
|
||||
description: game.i18n.localize(statusData.description),
|
||||
tint: effect.tint
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/* Temporarily copying the foundry method to add a fix to a bug with scenes
|
||||
https://discord.com/channels/170995199584108546/1296292044011995136/1446693077443149856
|
||||
*/
|
||||
getDependentTokens({ scenes, linked = false } = {}) {
|
||||
if (this.isToken && !scenes) return [this.token];
|
||||
if (scenes) scenes = Array.isArray(scenes) ? scenes : [scenes];
|
||||
else scenes = Array.from(this._dependentTokens.keys());
|
||||
|
||||
/* Code to filter out nonexistant scenes */
|
||||
scenes = scenes.filter(scene => game.scenes.some(x => x.id === scene.id));
|
||||
|
||||
if (this.isToken) {
|
||||
const parent = this.token.parent;
|
||||
return scenes.includes(parent) ? [this.token] : [];
|
||||
}
|
||||
|
||||
const allTokens = [];
|
||||
for (const scene of scenes) {
|
||||
if (!scene) continue;
|
||||
const tokens = this._dependentTokens.get(scene);
|
||||
for (const token of tokens ?? []) {
|
||||
if (!linked || token.actorLink) allTokens.push(token);
|
||||
}
|
||||
}
|
||||
|
||||
return allTokens;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,4 +16,54 @@ export default class DhpCombat extends Combat {
|
|||
|
||||
return a.name.localeCompare(b.name);
|
||||
}
|
||||
|
||||
async toggleModifierEffects(add, actors, category, groupingKey) {
|
||||
const effectData = category && groupingKey ? [{ category, grouping: groupingKey }] : this.system.battleToggles;
|
||||
if (add) {
|
||||
const effects = effectData.reduce((acc, toggle) => {
|
||||
const grouping = CONFIG.DH.ENCOUNTER.BPModifiers[toggle.category]?.[toggle.grouping];
|
||||
if (!grouping?.effects?.length) return acc;
|
||||
acc.push(
|
||||
...grouping.effects.map(effect => ({
|
||||
...effect,
|
||||
name: game.i18n.localize(effect.name),
|
||||
description: game.i18n.localize(effect.description),
|
||||
effectTargetTypes: grouping.effectTargetTypes ?? [],
|
||||
flags: {
|
||||
[`${CONFIG.DH.id}.${CONFIG.DH.FLAGS.combatToggle}`]: {
|
||||
category: toggle.category,
|
||||
grouping: toggle.grouping
|
||||
}
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (!effects.length) return;
|
||||
|
||||
for (let actor of actors) {
|
||||
await actor.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
effects.filter(x => x.effectTargetTypes.includes(actor.type))
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (let actor of actors) {
|
||||
await actor.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
actor.effects
|
||||
.filter(x => {
|
||||
const flag = x.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.combatToggle);
|
||||
if (!flag) return false;
|
||||
return effectData.some(
|
||||
data => flag.category == data.category && flag.grouping === data.grouping
|
||||
);
|
||||
})
|
||||
.map(x => x.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
module/documents/combatant.mjs
Normal file
6
module/documents/combatant.mjs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export default class DhCombatant extends Combatant {
|
||||
/**@inheritdoc */
|
||||
get isNPC() {
|
||||
return this.actor?.isNPC ?? (!this.actor || !this.hasPlayerOwner);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,13 @@ export default class DHItem extends foundry.documents.Item {
|
|||
return doc;
|
||||
}
|
||||
|
||||
static async createDocuments(sources, operation) {
|
||||
// Ensure that items being created are valid to the actor its being added to
|
||||
const actor = operation.parent;
|
||||
sources = actor?.system?.isItemValid ? sources.filter((s) => actor.system.isItemValid(s)) : sources;
|
||||
return super.createDocuments(sources, operation);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
|
|||
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