mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-05 20:34:15 +02:00
Merge branch 'main' into feature/granular-action-outcomes
This commit is contained in:
commit
482f712086
31 changed files with 221 additions and 195 deletions
|
|
@ -64,7 +64,7 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart
|
|||
|
||||
## Contributing
|
||||
|
||||
Looking to contribute to the project? Look no further, check out our [contributing guide](contributing.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
||||
Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
|
||||
|
||||
## Disclaimer:
|
||||
|
||||
|
|
|
|||
|
|
@ -342,7 +342,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
|
|||
const party = game.actors.get(data.partyId);
|
||||
if (!party) return;
|
||||
|
||||
const dialog = new game.system.api.applications.dialogs.TagTeamDialog(party);
|
||||
const TagTeamDialog = game.system.api.applications.dialogs.TagTeamDialog;
|
||||
const dialog = foundry.applications.instances.get(`TagTeamDialog-${party.id}`) ?? new TagTeamDialog(party);
|
||||
dialog.tabGroups.application = 'tagTeamRoll';
|
||||
await dialog.render({ force: true });
|
||||
}
|
||||
|
|
@ -353,7 +354,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
|
|||
const party = game.actors.get(data.partyId);
|
||||
if (!party) return;
|
||||
|
||||
const dialog = new game.system.api.applications.dialogs.GroupRollDialog(party);
|
||||
const GroupRollDialog = game.system.api.applications.dialogs.GroupRollDialog;
|
||||
const dialog = foundry.applications.instances.get(`GroupRollDialog-${party.id}`) ?? new GroupRollDialog(party);
|
||||
dialog.tabGroups.application = 'groupRoll';
|
||||
await dialog.render({ force: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3221,7 +3221,8 @@
|
|||
"domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used",
|
||||
"knowTheTide": "Know The Tide gained a token",
|
||||
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}",
|
||||
"noTokenTargeted": "No token is targeted"
|
||||
"noTokenTargeted": "No token is targeted",
|
||||
"behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM"
|
||||
},
|
||||
"Progress": {
|
||||
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
);
|
||||
|
||||
const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
|
||||
const armor = orderedArmorSources.reduce((acc, { document }) => {
|
||||
const armor = orderedArmorSources.reduce((acc, { name, document }) => {
|
||||
const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
|
||||
acc.push({
|
||||
name,
|
||||
effect: document,
|
||||
marks: [...Array(max).keys()].reduce((acc, _, index) => {
|
||||
const spent = index < current;
|
||||
|
|
@ -152,14 +153,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
const armorSources = [];
|
||||
for (const source of this.marks.armor) {
|
||||
const parent = source.effect.origin
|
||||
? await foundry.utils.fromUuid(source.effect.origin)
|
||||
: source.effect.parent;
|
||||
|
||||
const useEffectName = parent.type === 'armor' || parent instanceof Actor;
|
||||
const label = useEffectName ? source.effect.name : parent.name;
|
||||
armorSources.push({
|
||||
label: label,
|
||||
label: source.name,
|
||||
uuid: source.effect.uuid,
|
||||
marks: source.marks
|
||||
});
|
||||
|
|
|
|||
|
|
@ -259,7 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
const resetValue = increasing
|
||||
? 0
|
||||
: feature.system.resource.max
|
||||
? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total
|
||||
? new Roll(
|
||||
Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData())
|
||||
).evaluateSync().total
|
||||
: 0;
|
||||
|
||||
await feature.update({ 'system.resource.value': resetValue });
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(party) {
|
||||
super();
|
||||
super({ id: `GroupRollDialog-${party.id}` });
|
||||
|
||||
this.party = party;
|
||||
this.partyMembers = party.system.partyMembers
|
||||
|
|
@ -35,7 +35,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'GroupRollDialog',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
|
||||
position: { width: 390, height: 'auto' },
|
||||
window: {
|
||||
|
|
@ -243,7 +242,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
});
|
||||
};
|
||||
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
gmUpdate,
|
||||
update,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { MemberData } from '../../data/tagTeamData.mjs';
|
||||
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(party) {
|
||||
super();
|
||||
super({ id: `TagTeamDialog-${party.id}` });
|
||||
|
||||
this.party = party;
|
||||
this.partyMembers = party.system.partyMembers
|
||||
|
|
@ -36,7 +36,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'TagTeamDialog',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
||||
position: { width: 550, height: 'auto' },
|
||||
actions: {
|
||||
|
|
@ -60,13 +59,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
id: 'initialization',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs'
|
||||
},
|
||||
tagTeamRoll: {
|
||||
id: 'tagTeamRoll',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs'
|
||||
},
|
||||
rollSelection: {
|
||||
id: 'rollSelection',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/rollSelection.hbs'
|
||||
},
|
||||
tagTeamRoll: {
|
||||
id: 'tagTeamRoll',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs'
|
||||
result: {
|
||||
id: 'result',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/result.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -97,36 +100,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
_configureRenderParts(options) {
|
||||
const { initialization, rollSelection, tagTeamRoll } = super._configureRenderParts(options);
|
||||
const augmentedParts = { initialization };
|
||||
const parts = super._configureRenderParts(options);
|
||||
for (const memberKey of Object.keys(this.party.system.tagTeam.members)) {
|
||||
augmentedParts[memberKey] = {
|
||||
parts[memberKey] = {
|
||||
id: memberKey,
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamMember.hbs'
|
||||
};
|
||||
}
|
||||
augmentedParts.rollSelection = rollSelection;
|
||||
augmentedParts.tagTeamRoll = tagTeamRoll;
|
||||
|
||||
return augmentedParts;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
// if (this.element.querySelector('.roll-selection')) {
|
||||
// for (const element of this.element.querySelectorAll('.team-member-container')) {
|
||||
// element.classList.add('select-padding');
|
||||
// }
|
||||
// }
|
||||
|
||||
if (this.element.querySelector('.team-container')) return;
|
||||
const initializationPart = this.element.querySelector('.initialization-container');
|
||||
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
|
||||
const teamContainer = this.element.querySelector('.team-container');
|
||||
for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
|
||||
teamContainer.appendChild(memberContainer);
|
||||
return parts;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
|
|
@ -167,6 +149,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
partContext.initiatorDisabled = !selectedMembers.length;
|
||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||
|
||||
break;
|
||||
case 'tagTeamRoll':
|
||||
partContext.memberKeys = Object.keys(this.party.system.tagTeam.members);
|
||||
break;
|
||||
case 'rollSelection':
|
||||
partContext.members = Object.keys(this.party.system.tagTeam.members).reduce((acc, key) => {
|
||||
|
|
@ -175,7 +160,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
return acc;
|
||||
}, {});
|
||||
break;
|
||||
case 'tagTeamRoll':
|
||||
case 'result':
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll
|
||||
? undefined
|
||||
|
|
@ -243,7 +228,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
getUpdatingParts(target) {
|
||||
const { initialization, rollSelection, tagTeamRoll } = this.constructor.PARTS;
|
||||
const { initialization, rollSelection, result } = this.constructor.PARTS;
|
||||
const isInitialization = this.tabGroups.application === initialization.id;
|
||||
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
|
||||
|
||||
|
|
@ -251,7 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
...(isInitialization ? [initialization.id] : []),
|
||||
...(updatingMember ? [updatingMember] : []),
|
||||
...(!isInitialization ? [rollSelection.id] : []),
|
||||
...(!isInitialization ? [tagTeamRoll.id] : [])
|
||||
...(!isInitialization ? [result.id] : [])
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +259,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
});
|
||||
};
|
||||
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
gmUpdate,
|
||||
update,
|
||||
|
|
|
|||
|
|
@ -120,12 +120,6 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
|||
foundry.utils.fromUuidSync(x)
|
||||
);
|
||||
|
||||
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
||||
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
||||
submitData.flags.daggerheart.sceneEnvironments[key] = _del;
|
||||
}
|
||||
}
|
||||
|
||||
super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
||||
|
|
@ -138,8 +139,4 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
async _onDropItem(event, item) {
|
||||
console.log(item);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -531,7 +531,7 @@ export default function DHApplicationMixin(Base) {
|
|||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && target.dataset.itemType !== 'beastform';
|
||||
return doc?.isOwner !== false && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
await this.data.updateSource(update);
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
start: newMax
|
||||
}
|
||||
});
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
@ -218,7 +218,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
? 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, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
@ -277,7 +277,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
|
||||
async updateFear(value) {
|
||||
return emitAsGM(
|
||||
return emitGMUpdate(
|
||||
GMUpdateEvent.UpdateFear,
|
||||
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
value
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
|
||||
/** @inheritdoc */
|
||||
|
|
@ -68,7 +68,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
|||
1
|
||||
)[0];
|
||||
newEnvironments.unshift(newFirst);
|
||||
emitAsGM(
|
||||
emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
scene.update.bind(scene),
|
||||
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
||||
|
|
|
|||
|
|
@ -57,14 +57,14 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
|||
}
|
||||
|
||||
async placeRegion(data, options = {}) {
|
||||
const preConfirm = ({ _event, document, _create, _options }) => {
|
||||
const shape = document.shapes[0];
|
||||
const preConfirm = data => {
|
||||
const shape = data.document.shapes[0];
|
||||
const isEmanation = shape.type === 'emanation';
|
||||
if (isEmanation) {
|
||||
const token = this.#findTokenInBounds(shape.base.origin);
|
||||
if (!token) return options.preConfirm?.() ?? true;
|
||||
if (!token) return options.preConfirm?.(data) ?? true;
|
||||
const shapeData = shape.toObject();
|
||||
document.updateSource({
|
||||
data.document.updateSource({
|
||||
shapes: [
|
||||
{
|
||||
...shapeData,
|
||||
|
|
@ -80,10 +80,10 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
|||
});
|
||||
}
|
||||
|
||||
return options?.preConfirm?.() ?? true;
|
||||
return options?.preConfirm?.(data) ?? true;
|
||||
};
|
||||
|
||||
super.placeRegion(data, { ...options, preConfirm });
|
||||
return await super.placeRegion(data, { ...options, preConfirm });
|
||||
}
|
||||
|
||||
/** Searches for token at origin point, returning null if there are no tokens or multiple overlapping tokens */
|
||||
|
|
|
|||
|
|
@ -836,10 +836,10 @@ export default class DhCharacter extends DhCreature {
|
|||
|
||||
/* Scars can alter the amount of current hope */
|
||||
if (changes.system?.scars) {
|
||||
const diff = this.system.scars - changes.system.scars;
|
||||
const newHopeMax = this.system.resources.hope.max + diff;
|
||||
const newHopeValue = Math.min(newHopeMax, this.system.resources.hope.value);
|
||||
if (newHopeValue != this.system.resources.hope.value) {
|
||||
const diff = this.scars - changes.system.scars;
|
||||
const newHopeMax = this.resources.hope.max + diff;
|
||||
const newHopeValue = Math.min(newHopeMax, this.resources.hope.value);
|
||||
if (newHopeValue != this.resources.hope.value) {
|
||||
if (!changes.system.resources.hope) changes.system.resources.hope = { value: 0 };
|
||||
|
||||
changes.system.resources.hope = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ export default class CountdownField extends fields.ArrayField {
|
|||
);
|
||||
}
|
||||
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateCountdowns,
|
||||
async () => {
|
||||
const countdownSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent } from '../../../systemRegistration/socket.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ export default class EffectsField extends fields.ArrayField {
|
|||
}
|
||||
if (EffectsField.getAutomation() || force) {
|
||||
targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit);
|
||||
await emitAsGM(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid);
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateEffect, EffectsField.applyEffects.bind(this), targets, this.uuid);
|
||||
// EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export default class DHRoll extends Roll {
|
|||
if (!roll.terms[i].isDeterministic) continue;
|
||||
const termTotal = roll.terms[i].total;
|
||||
if (typeof termTotal === 'number') {
|
||||
const multiplier = roll.terms[i - 1]?.operator === ' - ' ? -1 : 1;
|
||||
const multiplier = roll.terms[i - 1]?.operator === '-' ? -1 : 1;
|
||||
modifierTotal += multiplier * termTotal;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
|
||||
/** Recursively finds the first parent document of the given object */
|
||||
static #resolveParentDocument(model, documentClass) {
|
||||
if (!model) return null;
|
||||
return model instanceof documentClass
|
||||
? model
|
||||
: model.parent
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||
import { LevelOptionType } from '../data/levelTier.mjs';
|
||||
import DHFeature from '../data/item/feature.mjs';
|
||||
import { createScrollText, damageKeyToNumber, getDamageKey, createShallowProxy } from '../helpers/utils.mjs';
|
||||
|
|
@ -827,7 +827,7 @@ export default class DhpActor extends Actor {
|
|||
const u = updates[key];
|
||||
if (key === 'items') {
|
||||
Object.values(u).forEach(async item => {
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
item.target.update.bind(item.target),
|
||||
item.resources,
|
||||
|
|
@ -836,7 +836,7 @@ export default class DhpActor extends Actor {
|
|||
});
|
||||
} else {
|
||||
if (Object.keys(u.resources).length > 0) {
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
u.target.update.bind(u.target),
|
||||
u.resources,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, emitGMCreate, GMUpdateEvent } from '../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||
targetHook = null;
|
||||
|
|
@ -214,7 +214,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
const action = this.system.action;
|
||||
if (!action || !action?.hasSave) return;
|
||||
game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result =>
|
||||
emitAsGM(
|
||||
emitGMUpdate(
|
||||
GMUpdateEvent.UpdateSaveMessage,
|
||||
game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(
|
||||
action,
|
||||
|
|
@ -259,27 +259,47 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
const { shape: type, size: range } = selectedArea;
|
||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
|
||||
|
||||
await canvas.regions.placeRegion(
|
||||
{
|
||||
name: selectedArea.name,
|
||||
shapes: [shapeData],
|
||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||
behaviors: [
|
||||
{
|
||||
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
|
||||
type: 'applyActiveEffect',
|
||||
system: {
|
||||
effects: effects
|
||||
}
|
||||
}
|
||||
],
|
||||
displayMeasurements: true,
|
||||
locked: false,
|
||||
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
|
||||
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
||||
},
|
||||
{ create: true }
|
||||
);
|
||||
const scene = game.scenes.get(game.user.viewedScene);
|
||||
const level = scene.levels.find(x => x.isView);
|
||||
|
||||
const regionData = {
|
||||
name: selectedArea.name,
|
||||
levels: level ? [level.id] : [],
|
||||
shapes: [shapeData],
|
||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||
behaviors:
|
||||
effects.length > 0
|
||||
? [
|
||||
{
|
||||
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
|
||||
type: 'applyActiveEffect',
|
||||
system: {
|
||||
effects: effects
|
||||
}
|
||||
}
|
||||
]
|
||||
: [],
|
||||
displayMeasurements: true,
|
||||
locked: false,
|
||||
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
|
||||
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
||||
};
|
||||
const placeRegion = data => {
|
||||
canvas.regions.placeRegion(data, { create: true });
|
||||
};
|
||||
|
||||
// Regions with effects must be placed by the GM
|
||||
if (effects.length > 0 && !game.user.isGM) {
|
||||
if (!game.users.activeGM)
|
||||
return ui.notifications.error(
|
||||
game.i18n.localize('DAGGERHEART.UI.Notifications.behaviorRegionRequiresGM')
|
||||
);
|
||||
|
||||
const region = await canvas.regions.placeRegion(regionData, { create: false });
|
||||
emitGMCreate('Region', placeRegion, region, scene.id);
|
||||
} else {
|
||||
placeRegion(regionData);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.system.action.areas.length === 1) createArea(this.system.action.areas[0]);
|
||||
|
|
|
|||
|
|
@ -757,9 +757,12 @@ export function getArmorSources(actor) {
|
|||
// Get the origin item. Since the actor is already loaded, it should already be cached
|
||||
// Consider the relative function versions if this causes an issue
|
||||
const origin = doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc;
|
||||
const useParentName = doc.parent && !(doc.parent instanceof Actor);
|
||||
const name = doc.origin || !useParentName ? doc.name : doc.parent.name;
|
||||
|
||||
return {
|
||||
origin,
|
||||
name: origin.name,
|
||||
name,
|
||||
document: doc,
|
||||
data: doc.system.armor ?? doc.system.armorData,
|
||||
disabled: !!doc.disabled || !!doc.isSuppressed
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
|
|||
case socketEvent.GMUpdate:
|
||||
Hooks.callAll(socketEvent.GMUpdate, data);
|
||||
break;
|
||||
case socketEvent.GMCreate:
|
||||
Hooks.callAll(socketEvent.GMCreate, data);
|
||||
break;
|
||||
case socketEvent.DhpFearUpdate:
|
||||
Hooks.callAll(socketEvent.DhpFearUpdate);
|
||||
break;
|
||||
|
|
@ -25,6 +28,7 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
|
|||
|
||||
export const socketEvent = {
|
||||
GMUpdate: 'DhGMUpdate',
|
||||
GMCreate: 'DhGMCreate',
|
||||
Refresh: 'DhRefresh',
|
||||
DhpFearUpdate: 'DhFearUpdate',
|
||||
DowntimeTrigger: 'DowntimeTrigger',
|
||||
|
|
@ -56,14 +60,14 @@ export const registerSocketHooks = () => {
|
|||
const document = data.uuid ? await fromUuid(data.uuid) : null;
|
||||
switch (data.action) {
|
||||
case GMUpdateEvent.UpdateDocument:
|
||||
if (document && data.update) await document.update(data.update);
|
||||
if (document && data.data) await document.update(data.data);
|
||||
break;
|
||||
case GMUpdateEvent.UpdateEffect:
|
||||
if (document && data.update)
|
||||
await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.update);
|
||||
if (document && data.data)
|
||||
await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.data);
|
||||
break;
|
||||
case GMUpdateEvent.UpdateSetting:
|
||||
await game.settings.set(CONFIG.DH.id, data.uuid, data.update);
|
||||
await game.settings.set(CONFIG.DH.id, data.uuid, data.data);
|
||||
break;
|
||||
case GMUpdateEvent.UpdateFear:
|
||||
await game.settings.set(
|
||||
|
|
@ -73,22 +77,22 @@ export const registerSocketHooks = () => {
|
|||
0,
|
||||
Math.min(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
data.update
|
||||
data.data
|
||||
)
|
||||
)
|
||||
);
|
||||
break;
|
||||
case GMUpdateEvent.UpdateCountdowns:
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data.update);
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns, data.data);
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.Countdown });
|
||||
break;
|
||||
case GMUpdateEvent.UpdateSaveMessage:
|
||||
const message = game.messages.get(data.update.message);
|
||||
const message = game.messages.get(data.data.message);
|
||||
if (!message) return;
|
||||
game.system.api.fields.ActionFields.SaveField.updateSaveMessage(
|
||||
data.update.result,
|
||||
data.data.result,
|
||||
message,
|
||||
data.update.token
|
||||
data.data.token
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
|
@ -102,6 +106,17 @@ export const registerSocketHooks = () => {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.on(socketEvent.GMCreate, async ({ data, documentType, scene }) => {
|
||||
if (!game.user.isGM) return;
|
||||
|
||||
switch (documentType) {
|
||||
default:
|
||||
const cls = getDocumentClass(documentType);
|
||||
cls.create(data, { parent: game.scenes.get(scene) });
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const registerUserQueries = () => {
|
||||
|
|
@ -109,18 +124,21 @@ export const registerUserQueries = () => {
|
|||
CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery;
|
||||
};
|
||||
|
||||
export const emitAsGM = async (eventName, callback, update, uuid = null, refresh = null) => {
|
||||
export const emitGMUpdate = async (eventName, callback, update, uuid = null, refresh = null) => {
|
||||
return await emitAsGM(socketEvent.GMUpdate, { action: eventName, callback, data: update, uuid, refresh });
|
||||
};
|
||||
|
||||
export const emitGMCreate = async (documentType, callback, data, scene) => {
|
||||
return await emitAsGM(socketEvent.GMCreate, { documentType, callback, data, scene });
|
||||
};
|
||||
|
||||
export const emitAsGM = async (event, data = { callback: () => {}, data: {} }) => {
|
||||
if (!game.user.isGM) {
|
||||
return await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: eventName,
|
||||
uuid,
|
||||
update,
|
||||
refresh
|
||||
}
|
||||
action: event,
|
||||
data: data
|
||||
});
|
||||
} else return callback(update);
|
||||
} else return data.callback(data.data);
|
||||
};
|
||||
|
||||
export const emitAsOwner = (eventName, userId, args) => {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@
|
|||
"experiences": {
|
||||
"ti3Z1mq2M92KK4GJ": {
|
||||
"name": "Bloodthirsty",
|
||||
"description": ""
|
||||
"description": "",
|
||||
"value": 3
|
||||
}
|
||||
},
|
||||
"bonuses": {
|
||||
|
|
@ -242,27 +243,24 @@
|
|||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.difficulty",
|
||||
"value": 3,
|
||||
"priority": null,
|
||||
"type": "add"
|
||||
}
|
||||
]
|
||||
},
|
||||
"_id": "qZfNiqw1iAIxeuYg",
|
||||
"img": "icons/commodities/biological/wing-lizard-brown.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.difficulty",
|
||||
"mode": 2,
|
||||
"value": "3",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
"value": null,
|
||||
"units": "seconds",
|
||||
"expiry": null,
|
||||
"expired": false
|
||||
},
|
||||
"description": "<p>While flying, the Bat gains a +3 bonus to their Difficulty.</p>",
|
||||
"origin": null,
|
||||
|
|
@ -274,6 +272,9 @@
|
|||
"_stats": {
|
||||
"compendiumSource": null
|
||||
},
|
||||
"start": null,
|
||||
"showIcon": 1,
|
||||
"folder": null,
|
||||
"_key": "!actors.items.effects!tBWHW00epmMnkawe.gx22MpD8fWoi8klZ.qZfNiqw1iAIxeuYg"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@
|
|||
"name": "Crushing Blows",
|
||||
"type": "feature",
|
||||
"system": {
|
||||
"description": "<p>When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.</p>",
|
||||
"description": "<p>When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"0sXciTiPc30v8czv": {
|
||||
|
|
|
|||
|
|
@ -138,12 +138,9 @@
|
|||
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
|
||||
"anchorX": 0.5,
|
||||
"anchorY": 0.5,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"fit": "contain",
|
||||
"scaleX": 1,
|
||||
"scaleY": 1,
|
||||
"rotation": 0,
|
||||
"tint": "#ffffff",
|
||||
"alphaThreshold": 0.75
|
||||
},
|
||||
|
|
@ -194,7 +191,7 @@
|
|||
"saturation": 0,
|
||||
"contrast": 0
|
||||
},
|
||||
"detectionModes": [],
|
||||
"detectionModes": {},
|
||||
"occludable": {
|
||||
"radius": 0
|
||||
},
|
||||
|
|
@ -220,7 +217,8 @@
|
|||
"flags": {},
|
||||
"randomImg": false,
|
||||
"appendNumber": false,
|
||||
"prependAdjective": false
|
||||
"prependAdjective": false,
|
||||
"depth": 1
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
|
|
@ -257,7 +255,7 @@
|
|||
"name": "Acidic Form",
|
||||
"type": "feature",
|
||||
"system": {
|
||||
"description": "<p>When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefi ts (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.</p>",
|
||||
"description": "<p>When the @Lookup[@name] makes a successful attack, the target must mark an Armor Slot without receiving its benefits (they can still use armor to reduce the damage). If they can’t mark an Armor Slot, they must mark an additional HP.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"gtT2oHSyZg9OHHJD": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "daggerheart",
|
||||
"title": "Daggerheart",
|
||||
"description": "An unofficial implementation of the Daggerheart system",
|
||||
"version": "2.2.1",
|
||||
"version": "2.2.3",
|
||||
"compatibility": {
|
||||
"minimum": "14.359",
|
||||
"verified": "14.360",
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"url": "https://github.com/Foundryborne/daggerheart",
|
||||
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
|
||||
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.1/system.zip",
|
||||
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.3/system.zip",
|
||||
"authors": [
|
||||
{
|
||||
"name": "WBHarry"
|
||||
|
|
|
|||
38
templates/dialogs/tagTeamDialog/result.hbs
Normal file
38
templates/dialogs/tagTeamDialog/result.hbs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<section class="tag-team-roll tab {{#if tabs.tagTeamRoll.active}} active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}">
|
||||
<div class="tag-team-roll-container {{#unless isEditable}}inactive{{/unless}}">
|
||||
<div class="results-container">
|
||||
<span class="result-container-label">{{localize "DAGGERHEART.GENERAL.result.plural"}}</span>
|
||||
<div class="results-inner-container">
|
||||
{{#if hintText}}
|
||||
<div class="hint">{{localize hintText}}</div>
|
||||
{{else}}
|
||||
{{#if joinedRoll.roll}}
|
||||
<div class="result-container">
|
||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
|
||||
<div class="result-info">
|
||||
<div class="damage-info">{{joinedRoll.roll.total}}</div>
|
||||
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if joinedRoll.rollData.options.hasDamage}}
|
||||
<div class="result-container">
|
||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
|
||||
{{#each joinedRoll.rollData.options.damage as |damage key|}}
|
||||
<div class="result-info">
|
||||
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
|
||||
<div class="damage-info">{{damage.total}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="finish-container">
|
||||
<button type="button" data-action="cancelRoll">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelTagTeamRoll"}}</button>
|
||||
<button type="button" data-action="finishRoll" {{#if hintText}}disabled{{/if}} class="finish-button">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.finishTagTeamRoll"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -1,38 +1,9 @@
|
|||
<section class="tag-team-roll tab {{#if tabs.tagTeamRoll.active}} active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}">
|
||||
<div class="tag-team-roll-container {{#unless isEditable}}inactive{{/unless}}">
|
||||
<div class="results-container">
|
||||
<span class="result-container-label">{{localize "DAGGERHEART.GENERAL.result.plural"}}</span>
|
||||
<div class="results-inner-container">
|
||||
{{#if hintText}}
|
||||
<div class="hint">{{localize hintText}}</div>
|
||||
{{else}}
|
||||
{{#if joinedRoll.roll}}
|
||||
<div class="result-container">
|
||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
|
||||
<div class="result-info">
|
||||
<div class="damage-info">{{joinedRoll.roll.total}}</div>
|
||||
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if joinedRoll.rollData.options.hasDamage}}
|
||||
<div class="result-container">
|
||||
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
|
||||
{{#each joinedRoll.rollData.options.damage as |damage key|}}
|
||||
<div class="result-info">
|
||||
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
|
||||
<div class="damage-info">{{damage.total}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="finish-container">
|
||||
<button type="button" data-action="cancelRoll">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelTagTeamRoll"}}</button>
|
||||
<button type="button" data-action="finishRoll" {{#if hintText}}disabled{{/if}} class="finish-button">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.finishTagTeamRoll"}}</button>
|
||||
</div>
|
||||
<div class="tab {{#if tabs.tagTeamRoll.active}}active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}">
|
||||
<div class="team-container">
|
||||
{{#each memberKeys as |key|}}
|
||||
<div data-application-part="{{key}}"></div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</section>
|
||||
<div data-application-part="rollSelection"></div>
|
||||
<div data-application-part="result"></div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue