diff --git a/daggerheart.mjs b/daggerheart.mjs
index 43aafce4..240d8704 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -343,17 +343,6 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
}
});
-Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
- if (data.openForAllPlayers && data.partyId) {
- const party = game.actors.get(data.partyId);
- if (!party) return;
-
- const dialog = new game.system.api.applications.dialogs.GroupRollDialog(party);
- dialog.tabGroups.application = 'groupRoll';
- await dialog.render({ force: true });
- }
-});
-
const updateActorsRangeDependentEffects = async token => {
const rangeMeasurement = game.settings.get(
CONFIG.DH.id,
diff --git a/lang/en.json b/lang/en.json
index 41441b61..f94a7325 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -706,7 +706,7 @@
"FIELDS": {
"initiator": {
"memberId": { "label": "Initiating Character" },
- "cost": { "label": "Hope Cost" }
+ "cost": { "label": "Initiation Cost" }
}
},
"leaderTitle": "Initiating Character",
@@ -733,17 +733,6 @@
"selectRoll": "Select which roll value to be used for the Tag Team"
}
},
- "GroupRollSelect": {
- "title": "Group Roll",
- "leader": "Leader",
- "leaderRoll": "Leader Roll",
- "openDialogForAll": "Open Dialog For All",
- "startGroupRoll": "Start Group Roll",
- "cancelGroupRoll": "Cancel",
- "finishGroupRoll": "Finish Group Roll",
- "cancelConfirmTitle": "Cancel Group Roll",
- "cancelConfirmText": "Are you sure you want to cancel the Group Roll? This will close it for all other players too."
- },
"TokenConfig": {
"actorSizeUsed": "Actor size is set, determining the dimensions"
}
@@ -2993,6 +2982,18 @@
"immunityTo": "Immunity: {immunities}"
},
"featureTitle": "Class Feature",
+ "groupRoll": {
+ "title": "Group Roll",
+ "leader": "Leader",
+ "partyTeam": "Party Team",
+ "team": "Team",
+ "selectLeader": "Select a Leader",
+ "selectMember": "Select a Member",
+ "rerollTitle": "Reroll Group Roll",
+ "rerollContent": "Are you sure you want to reroll your {trait} roll?",
+ "rerollTooltip": "Reroll",
+ "wholePartySelected": "The whole party is selected"
+ },
"healingRoll": {
"title": "Heal - {damage}",
"heal": "Heal",
diff --git a/module/applications/dialogs/_module.mjs b/module/applications/dialogs/_module.mjs
index c866f1cd..a479100a 100644
--- a/module/applications/dialogs/_module.mjs
+++ b/module/applications/dialogs/_module.mjs
@@ -13,7 +13,7 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
+export { default as GroupRollDialog } from './group-roll-dialog.mjs';
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
-export { default as GroupRollDialog } from './groupRollDialog.mjs';
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
diff --git a/module/applications/dialogs/group-roll-dialog.mjs b/module/applications/dialogs/group-roll-dialog.mjs
new file mode 100644
index 00000000..8a3c43d6
--- /dev/null
+++ b/module/applications/dialogs/group-roll-dialog.mjs
@@ -0,0 +1,204 @@
+import autocomplete from 'autocompleter';
+import { abilities } from '../../config/actorConfig.mjs';
+
+const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
+
+export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
+ constructor(actors) {
+ super();
+ this.actors = actors;
+ this.actorLeader = {};
+ this.actorsMembers = [];
+ }
+
+ get title() {
+ return 'Group Roll';
+ }
+
+ static DEFAULT_OPTIONS = {
+ tag: 'form',
+ classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'],
+ position: { width: 'auto', height: 'auto' },
+ window: {
+ title: 'DAGGERHEART.UI.Chat.groupRoll.title'
+ },
+ actions: {
+ roll: GroupRollDialog.#roll,
+ removeLeader: GroupRollDialog.#removeLeader,
+ removeMember: GroupRollDialog.#removeMember
+ },
+ form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
+ };
+
+ static PARTS = {
+ application: {
+ id: 'group-roll',
+ template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs'
+ }
+ };
+
+ _attachPartListeners(partId, htmlElement, options) {
+ super._attachPartListeners(partId, htmlElement, options);
+ const leaderChoices = this.actors.filter(x => this.actorsMembers.every(member => member.actor?.id !== x.id));
+ const memberChoices = this.actors.filter(
+ x => this.actorLeader?.actor?.id !== x.id && this.actorsMembers.every(member => member.actor?.id !== x.id)
+ );
+
+ htmlElement.querySelectorAll('.leader-change-input').forEach(element => {
+ autocomplete({
+ input: element,
+ fetch: function (text, update) {
+ if (!text) {
+ update(leaderChoices);
+ } else {
+ text = text.toLowerCase();
+ var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text));
+ update(suggestions);
+ }
+ },
+ render: function (actor, search) {
+ const actorName = game.i18n.localize(actor.name);
+ const matchIndex = actorName.toLowerCase().indexOf(search);
+
+ const beforeText = actorName.slice(0, matchIndex);
+ const matchText = actorName.slice(matchIndex, matchIndex + search.length);
+ const after = actorName.slice(matchIndex + search.length, actorName.length);
+ const img = document.createElement('img');
+ img.src = actor.img;
+
+ const element = document.createElement('li');
+ element.appendChild(img);
+
+ const label = document.createElement('span');
+ label.innerHTML =
+ `${beforeText}${matchText ? `${matchText} ` : ''}${after}`.replaceAll(
+ ' ',
+ ' '
+ );
+ element.appendChild(label);
+
+ return element;
+ },
+ renderGroup: function (label) {
+ const itemElement = document.createElement('div');
+ itemElement.textContent = game.i18n.localize(label);
+ return itemElement;
+ },
+ onSelect: actor => {
+ element.value = actor.uuid;
+ this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 };
+ this.render();
+ },
+ click: e => e.fetch(),
+ customize: function (_input, _inputRect, container) {
+ container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
+ },
+ minLength: 0
+ });
+ });
+
+ htmlElement.querySelectorAll('.team-push-input').forEach(element => {
+ autocomplete({
+ input: element,
+ fetch: function (text, update) {
+ if (!text) {
+ update(memberChoices);
+ } else {
+ text = text.toLowerCase();
+ var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text));
+ update(suggestions);
+ }
+ },
+ render: function (actor, search) {
+ const actorName = game.i18n.localize(actor.name);
+ const matchIndex = actorName.toLowerCase().indexOf(search);
+
+ const beforeText = actorName.slice(0, matchIndex);
+ const matchText = actorName.slice(matchIndex, matchIndex + search.length);
+ const after = actorName.slice(matchIndex + search.length, actorName.length);
+ const img = document.createElement('img');
+ img.src = actor.img;
+
+ const element = document.createElement('li');
+ element.appendChild(img);
+
+ const label = document.createElement('span');
+ label.innerHTML =
+ `${beforeText}${matchText ? `${matchText} ` : ''}${after}`.replaceAll(
+ ' ',
+ ' '
+ );
+ element.appendChild(label);
+
+ return element;
+ },
+ renderGroup: function (label) {
+ const itemElement = document.createElement('div');
+ itemElement.textContent = game.i18n.localize(label);
+ return itemElement;
+ },
+ onSelect: actor => {
+ element.value = actor.uuid;
+ this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 });
+ this.render({ force: true });
+ },
+ click: e => e.fetch(),
+ customize: function (_input, _inputRect, container) {
+ container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
+ },
+ minLength: 0
+ });
+ });
+ }
+
+ async _prepareContext(_options) {
+ const context = await super._prepareContext(_options);
+ context.leader = this.actorLeader;
+ context.members = this.actorsMembers;
+ context.traitList = abilities;
+
+ context.allSelected = this.actorsMembers.length + (this.actorLeader?.actor ? 1 : 0) === this.actors.length;
+ context.rollDisabled = context.members.length === 0 || !this.actorLeader?.actor;
+
+ return context;
+ }
+
+ static updateData(event, _, formData) {
+ const { actorLeader, actorsMembers } = foundry.utils.expandObject(formData.object);
+ this.actorLeader = foundry.utils.mergeObject(this.actorLeader, actorLeader);
+ this.actorsMembers = foundry.utils.mergeObject(this.actorsMembers, actorsMembers);
+ this.render(true);
+ }
+
+ static async #removeLeader(_, button) {
+ this.actorLeader = null;
+ this.render();
+ }
+
+ static async #removeMember(_, button) {
+ this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid);
+ this.render();
+ }
+
+ static async #roll() {
+ const cls = getDocumentClass('ChatMessage');
+ const systemData = {
+ leader: this.actorLeader,
+ members: this.actorsMembers
+ };
+ const msg = {
+ type: 'groupRoll',
+ user: game.user.id,
+ speaker: cls.getSpeaker(),
+ title: game.i18n.localize('DAGGERHEART.UI.Chat.groupRoll.title'),
+ system: systemData,
+ content: await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
+ { system: systemData }
+ )
+ };
+
+ cls.create(msg);
+ this.close();
+ }
+}
diff --git a/module/applications/dialogs/groupRollDialog.mjs b/module/applications/dialogs/groupRollDialog.mjs
deleted file mode 100644
index 2a7be791..00000000
--- a/module/applications/dialogs/groupRollDialog.mjs
+++ /dev/null
@@ -1,527 +0,0 @@
-import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
-import { emitAsGM, 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();
-
- this.party = party;
- this.partyMembers = party.system.partyMembers
- .filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
- .map(member => ({
- ...member.toObject(),
- uuid: member.uuid,
- id: member.id,
- selected: true,
- owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
- }));
-
- this.leader = null;
- this.openForAllPlayers = true;
-
- this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length
- ? 'groupRoll'
- : 'initialization';
-
- Hooks.on(socketEvent.Refresh, this.groupRollRefresh.bind());
- }
-
- get title() {
- return game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title');
- }
-
- static DEFAULT_OPTIONS = {
- tag: 'form',
- id: 'GroupRollDialog',
- classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
- position: { width: 550, height: 'auto' },
- actions: {
- toggleSelectMember: this.#toggleSelectMember,
- startGroupRoll: this.#startGroupRoll,
- makeRoll: this.#makeRoll,
- removeRoll: this.#removeRoll,
- rerollDice: this.#rerollDice,
- makeLeaderRoll: this.#makeLeaderRoll,
- removeLeaderRoll: this.#removeLeaderRoll,
- rerollLeaderDice: this.#rerollLeaderDice,
- markSuccessfull: this.#markSuccessfull,
- cancelRoll: this.#onCancelRoll,
- finishRoll: this.#finishRoll
- },
- form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
- };
-
- static PARTS = {
- initialization: {
- id: 'initialization',
- template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs'
- },
- leader: {
- id: 'leader',
- template: 'systems/daggerheart/templates/dialogs/groupRollDialog/leader.hbs'
- },
- groupRoll: {
- id: 'groupRoll',
- template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRoll.hbs'
- },
- footer: {
- id: 'footer',
- template: 'systems/daggerheart/templates/dialogs/groupRollDialog/footer.hbs'
- }
- };
-
- /** @inheritdoc */
- static TABS = {
- application: {
- tabs: [{ id: 'initialization' }, { id: 'groupRoll' }]
- }
- };
-
- _attachPartListeners(partId, htmlElement, options) {
- super._attachPartListeners(partId, htmlElement, options);
-
- htmlElement
- .querySelector('.main-character-field')
- ?.addEventListener('input', this.updateLeaderField.bind(this));
- }
-
- _configureRenderParts(options) {
- const { initialization, leader, groupRoll, footer } = super._configureRenderParts(options);
- const augmentedParts = { initialization };
- for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) {
- augmentedParts[memberKey] = {
- id: memberKey,
- template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMember.hbs'
- };
- }
-
- augmentedParts.leader = leader;
- augmentedParts.groupRoll = groupRoll;
- augmentedParts.footer = footer;
-
- return augmentedParts;
- }
-
- /**@inheritdoc */
- async _onRender(context, options) {
- await super._onRender(context, options);
-
- if (this.element.querySelector('.team-container')) return;
-
- if (this.tabGroups.application !== this.constructor.PARTS.initialization.id) {
- const initializationPart = this.element.querySelector('.initialization-container');
- initializationPart.insertAdjacentHTML('afterend', '
');
- initializationPart.insertAdjacentHTML(
- 'afterend',
- `${game.i18n.localize('Aiding Characters')}
`
- );
-
- const teamContainer = this.element.querySelector('.team-container');
- for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
- teamContainer.appendChild(memberContainer);
- }
- }
-
- async _prepareContext(_options) {
- const context = await super._prepareContext(_options);
-
- context.isGM = game.user.isGM;
- context.isEditable = this.getIsEditable();
- context.fields = this.party.system.schema.fields.groupRoll.fields;
- context.data = this.party.system.groupRoll;
- context.traitOptions = CONFIG.DH.ACTOR.abilities;
- context.members = {};
- context.allHaveRolled = Object.keys(context.data.participants).every(key => {
- const data = context.data.participants[key];
- return Boolean(data.rollData);
- });
-
- return context;
- }
-
- async _preparePartContext(partId, context, options) {
- const partContext = await super._preparePartContext(partId, context, options);
- partContext.partId = partId;
-
- switch (partId) {
- case 'initialization':
- partContext.groupRollFields = this.party.system.schema.fields.groupRoll.fields;
- partContext.memberSelection = this.partyMembers;
-
- const selectedMembers = partContext.memberSelection.filter(x => x.selected);
-
- partContext.selectedLeader = this.leader;
- partContext.selectedLeaderOptions = selectedMembers
- .filter(actor => actor.owned)
- .map(x => ({ value: x.id, label: x.name }));
- partContext.selectedLeaderDisabled = !selectedMembers.length;
-
- partContext.canStartGroupRoll = selectedMembers.length > 1 && this.leader?.memberId;
- partContext.openForAllPlayers = this.openForAllPlayers;
- break;
- case 'leader':
- partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
- break;
- case 'groupRoll':
- const leader = this.party.system.groupRoll.leader;
- partContext.hasRolled =
- leader?.rollData ||
- Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some(
- x => x.successfull !== null
- );
- const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce(
- (acc, curr) => {
- const modifier = curr.successfull === true ? 1 : curr.successfull === false ? -1 : null;
- if (modifier) {
- acc.modifierTotal += modifier;
- acc.modifiers.push(modifier);
- }
-
- return acc;
- },
- { modifierTotal: 0, modifiers: [] }
- );
- const leaderTotal = leader?.rollData ? leader.roll.total : null;
- partContext.groupRoll = {
- totalLabel: leader?.rollData
- ? game.i18n.format('DAGGERHEART.GENERAL.withThing', {
- thing: leader.roll.totalLabel
- })
- : null,
- totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear',
- total: leaderTotal + modifierTotal,
- leaderTotal: leaderTotal,
- modifiers
- };
- break;
- case 'footer':
- partContext.canFinishRoll =
- Boolean(this.party.system.groupRoll.leader?.rollData) &&
- Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successfull !== null);
- break;
- }
-
- if (Object.keys(this.party.system.groupRoll.aidingCharacters).includes(partId)) {
- const characterData = this.party.system.groupRoll.aidingCharacters[partId];
- partContext.members[partId] = this.getRollCharacterData(characterData, partId);
- }
-
- return partContext;
- }
-
- getRollCharacterData(data, partId) {
- if (!data) return {};
-
- const actor = game.actors.get(data.id);
-
- return {
- ...data,
- roll: data.roll,
- isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
- key: partId,
- readyToRoll: Boolean(data.rollChoice),
- hasRolled: Boolean(data.rollData)
- };
- }
-
- static async updateData(event, _, formData) {
- const partyData = foundry.utils.expandObject(formData.object);
-
- this.updatePartyData(partyData, this.getUpdatingParts(event.target));
- }
-
- async updatePartyData(update, updatingParts, options = { render: true }) {
- if (!game.users.activeGM)
- return ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.gmRequired'));
-
- const gmUpdate = async update => {
- await this.party.update(update);
- this.render({ parts: updatingParts });
- game.socket.emit(`system.${CONFIG.DH.id}`, {
- action: socketEvent.Refresh,
- data: { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts }
- });
- };
-
- await emitAsGM(
- GMUpdateEvent.UpdateDocument,
- gmUpdate,
- update,
- this.party.uuid,
- options.render ? { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } : undefined
- );
- }
-
- getUpdatingParts(target) {
- const { initialization, leader, groupRoll, footer } = this.constructor.PARTS;
- const isInitialization = this.tabGroups.application === initialization.id;
- const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
- const updatingLeader = target.closest('.main-character-outer-container');
-
- return [
- ...(isInitialization ? [initialization.id] : []),
- ...(updatingMember ? [updatingMember] : []),
- ...(updatingLeader ? [leader.id] : []),
- ...(!isInitialization ? [groupRoll.id, footer.id] : [])
- ];
- }
-
- getIsEditable() {
- return this.party.system.partyMembers.some(actor => {
- const selected = Boolean(this.party.system.groupRoll.participants[actor.id]);
- return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
- });
- }
-
- groupRollRefresh = ({ refreshType, action, parts }) => {
- if (refreshType !== RefreshType.GroupRoll) return;
-
- switch (action) {
- case 'startGroupRoll':
- this.tabGroups.application = 'groupRoll';
- break;
- case 'refresh':
- this.render({ parts });
- break;
- case 'close':
- this.close();
- break;
- }
- };
-
- 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(socketEvent.Refresh, this.groupRollRefresh);
- return super.close(options);
- }
-
- //#region Initialization
- static #toggleSelectMember(_, button) {
- const member = this.partyMembers.find(x => x.id === button.dataset.id);
- member.selected = !member.selected;
- this.render();
- }
-
- updateLeaderField(event) {
- if (!this.leader) this.leader = {};
- this.leader.memberId = event.target.value;
- this.render();
- }
-
- static async #startGroupRoll() {
- const leader = this.partyMembers.find(x => x.id === this.leader.memberId);
- const aidingCharacters = this.partyMembers.reduce((acc, curr) => {
- if (curr.selected && curr.id !== this.leader.memberId)
- acc[curr.id] = { id: curr.id, name: curr.name, img: curr.img };
-
- return acc;
- }, {});
-
- await this.party.update({
- 'system.groupRoll': _replace(
- new game.system.api.data.GroupRollData({
- ...this.party.system.groupRoll.toObject(),
- leader: { id: leader.id, name: leader.name, img: leader.img },
- aidingCharacters
- })
- )
- });
-
- const hookData = { openForAllPlayers: this.openForAllPlayers, partyId: this.party.id };
- Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, hookData);
- game.socket.emit(`system.${CONFIG.DH.id}`, {
- action: socketEvent.GroupRollStart,
- data: hookData
- });
-
- this.render();
- }
- //#endregion
-
- async makeRoll(button, characterData, path) {
- const actor = game.actors.find(x => x.id === characterData.id);
- if (!actor) return;
-
- const result = await actor.rollTrait(characterData.rollChoice, {
- skips: {
- createMessage: true,
- resources: true,
- triggers: true
- }
- });
-
- if (!result) return;
- if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
-
- const rollData = result.messageRoll.toJSON();
- delete rollData.options.messageRoll;
- this.updatePartyData(
- {
- [path]: rollData
- },
- this.getUpdatingParts(button)
- );
- }
-
- static async #makeRoll(_event, button) {
- const { member } = button.dataset;
- const character = this.party.system.groupRoll.aidingCharacters[member];
- this.makeRoll(button, character, `system.groupRoll.aidingCharacters.${member}.rollData`);
- }
-
- static async #makeLeaderRoll(_event, button) {
- const character = this.party.system.groupRoll.leader;
- this.makeRoll(button, character, 'system.groupRoll.leader.rollData');
- }
-
- async removeRoll(button, path) {
- this.updatePartyData(
- {
- [path]: {
- rollData: null,
- rollChoice: null,
- selected: false,
- successfull: null
- }
- },
- this.getUpdatingParts(button)
- );
- }
-
- static async #removeRoll(_event, button) {
- this.removeRoll(button, `system.groupRoll.aidingCharacters.${button.dataset.member}`);
- }
-
- static async #removeLeaderRoll(_event, button) {
- this.removeRoll(button, 'system.groupRoll.leader');
- }
-
- async rerollDice(button, data, path) {
- const { diceType } = button.dataset;
-
- const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2;
- const newRoll = game.system.api.dice.DualityRoll.fromData(data.rollData);
- const dice = newRoll.dice[dieIndex];
- await dice.reroll(`/r1=${dice.total}`, {
- liveRoll: {
- roll: newRoll,
- isReaction: true
- }
- });
- const rollData = newRoll.toJSON();
- this.updatePartyData(
- {
- [path]: rollData
- },
- this.getUpdatingParts(button)
- );
- }
-
- static async #rerollDice(_, button) {
- const { member } = button.dataset;
- this.rerollDice(
- button,
- this.party.system.groupRoll.aidingCharacters[member],
- `system.groupRoll.aidingCharacters.${member}.rollData`
- );
- }
-
- static async #rerollLeaderDice(_, button) {
- this.rerollDice(button, this.party.system.groupRoll.leader, `system.groupRoll.leader.rollData`);
- }
-
- static #markSuccessfull(_event, button) {
- const previousValue = this.party.system.groupRoll.aidingCharacters[button.dataset.member].successfull;
- const newValue = Boolean(button.dataset.successfull === 'true');
- this.updatePartyData(
- {
- [`system.groupRoll.aidingCharacters.${button.dataset.member}.successfull`]:
- previousValue === newValue ? null : newValue
- },
- this.getUpdatingParts(button)
- );
- }
-
- static async #onCancelRoll(_event, _button, options = { confirm: true }) {
- this.cancelRoll(options);
- }
-
- async cancelRoll(options = { confirm: true }) {
- if (options.confirm) {
- const confirmed = await foundry.applications.api.DialogV2.confirm({
- window: {
- title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmTitle')
- },
- content: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelConfirmText')
- });
-
- if (!confirmed) return;
- }
-
- await this.updatePartyData(
- {
- 'system.groupRoll': {
- leader: null,
- aidingCharacters: _replace({})
- }
- },
- [],
- { render: false }
- );
-
- this.close();
- game.socket.emit(`system.${CONFIG.DH.id}`, {
- action: socketEvent.Refresh,
- data: { refreshType: RefreshType.GroupRoll, action: 'close' }
- });
- }
-
- static async #finishRoll() {
- const totalRoll = this.party.system.groupRoll.leader.roll;
- for (const character of Object.values(this.party.system.groupRoll.aidingCharacters)) {
- totalRoll.terms.push(new foundry.dice.terms.OperatorTerm({ operator: character.successfull ? '+' : '-' }));
- totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 }));
- }
-
- await totalRoll._evaluate();
-
- const systemData = totalRoll.options;
- const actor = game.actors.get(this.party.system.groupRoll.leader.id);
-
- const cls = getDocumentClass('ChatMessage'),
- msgData = {
- type: 'dualityRoll',
- user: game.user.id,
- title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'),
- speaker: cls.getSpeaker({ actor }),
- system: systemData,
- rolls: [JSON.stringify(totalRoll)],
- sound: null,
- flags: { core: { RollTable: true } }
- };
-
- await cls.create(msgData);
-
- const resourceMap = new ResourceUpdateMap(actor);
- if (totalRoll.isCritical) {
- resourceMap.addResources([
- { key: 'stress', value: -1, total: 1 },
- { key: 'hope', value: 1, total: 1 }
- ]);
- } else if (totalRoll.withHope) {
- resourceMap.addResources([{ key: 'hope', value: 1, total: 1 }]);
- } else {
- resourceMap.addResources([{ key: 'fear', value: 1, total: 1 }]);
- }
-
- resourceMap.updateResources();
-
- /* Fin */
- this.cancelRoll({ confirm: false });
- }
-}
diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs
index 6e1654b0..054331b5 100644
--- a/module/applications/dialogs/tagTeamDialog.mjs
+++ b/module/applications/dialogs/tagTeamDialog.mjs
@@ -20,7 +20,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
}));
- this.initiator = { cost: 3 };
+ this.initiator = null;
this.openForAllPlayers = true;
this.tabGroups.application = Object.keys(party.system.tagTeam.members).length
diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs
index d4545f63..7c8c2338 100644
--- a/module/applications/sheets/actors/party.mjs
+++ b/module/applications/sheets/actors/party.mjs
@@ -4,6 +4,7 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs';
import FilterMenu from '../../ux/filter-menu.mjs';
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
import { socketEvent } from '../../../systemRegistration/socket.mjs';
+import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
import DhpActor from '../../../documents/actor.mjs';
export default class Party extends DHBaseActorSheet {
@@ -116,7 +117,6 @@ export default class Party extends DHBaseActorSheet {
relativeTo: this.document
});
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
- context.groupRollActive = Boolean(this.document.system.groupRoll.leader);
}
async _prepareMembersContext(context, _options) {
@@ -318,7 +318,9 @@ export default class Party extends DHBaseActorSheet {
}
static async #groupRoll(_params) {
- new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
+ new GroupRollDialog(
+ this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
+ ).render({ force: true });
}
/* -------------------------------------------- */
diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs
index 59939963..8cbacb09 100644
--- a/module/applications/ui/chatLog.mjs
+++ b/module/applications/ui/chatLog.mjs
@@ -1,6 +1,8 @@
+import { abilities } from '../../config/actorConfig.mjs';
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.mjs';
+import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor(options) {
@@ -148,6 +150,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(event, message))
);
+ html.querySelectorAll('.group-roll-button').forEach(element =>
+ element.addEventListener('click', event => this.groupRollButton(event, message))
+ );
+ html.querySelectorAll('.group-roll-reroll').forEach(element =>
+ element.addEventListener('click', event => this.groupRollReroll(event, message))
+ );
+ html.querySelectorAll('.group-roll-success').forEach(element =>
+ element.addEventListener('click', event => this.groupRollSuccessEvent(event, message))
+ );
+ html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
+ element.addEventListener('click', this.groupRollExpandSection)
+ );
html.querySelectorAll('.risk-it-all-button').forEach(element =>
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
);
@@ -291,6 +305,174 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
}
+ async groupRollButton(event, message) {
+ const path = event.currentTarget.dataset.path;
+ const isLeader = path === 'leader';
+ const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
+ const actor = game.actors.get(actorData._id);
+
+ if (!actor) {
+ return ui.notifications.error(
+ game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', {
+ documentType: game.i18n.localize('TYPES.Actor.character')
+ })
+ );
+ }
+
+ if (!actor.testUserPermission(game.user, 'OWNER')) {
+ return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
+ }
+
+ const traitLabel = game.i18n.localize(abilities[trait].label);
+ const config = {
+ event: event,
+ title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
+ headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
+ ability: traitLabel
+ }),
+ roll: {
+ trait: trait,
+ advantage: 0,
+ modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
+ },
+ hasRoll: true,
+ skips: {
+ createMessage: true,
+ resources: !isLeader,
+ updateCountdowns: !isLeader
+ }
+ };
+ const result = await actor.diceRoll({
+ ...config,
+ headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
+ title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
+ ability: traitLabel
+ })
+ });
+
+ if (!result) return;
+
+ const newMessageData = foundry.utils.deepClone(message.system);
+ foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
+ const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
+
+ const updatedContent = await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
+ { ...renderData, user: game.user }
+ );
+ const mess = game.messages.get(message._id);
+
+ await emitAsGM(
+ GMUpdateEvent.UpdateDocument,
+ mess.update.bind(mess),
+ {
+ ...renderData,
+ content: updatedContent
+ },
+ mess.uuid
+ );
+ }
+
+ async groupRollReroll(event, message) {
+ const path = event.currentTarget.dataset.path;
+ const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
+ const actor = game.actors.get(actorData._id);
+
+ if (!actor.testUserPermission(game.user, 'OWNER')) {
+ return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
+ }
+
+ const traitLabel = game.i18n.localize(abilities[trait].label);
+
+ const config = {
+ event: event,
+ title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
+ headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
+ ability: traitLabel
+ }),
+ roll: {
+ trait: trait,
+ advantage: 0,
+ modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
+ },
+ hasRoll: true,
+ skips: {
+ createMessage: true,
+ updateCountdowns: true
+ }
+ };
+ const result = await actor.diceRoll({
+ ...config,
+ headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
+ title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
+ ability: traitLabel
+ })
+ });
+
+ const newMessageData = foundry.utils.deepClone(message.system);
+ foundry.utils.setProperty(newMessageData, `${path}.result`, { ...result.roll, rerolled: true });
+ const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
+
+ const updatedContent = await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
+ { ...renderData, user: game.user }
+ );
+ const mess = game.messages.get(message._id);
+ await emitAsGM(
+ GMUpdateEvent.UpdateDocument,
+ mess.update.bind(mess),
+ {
+ ...renderData,
+ content: updatedContent
+ },
+ mess.uuid
+ );
+ }
+
+ async groupRollSuccessEvent(event, message) {
+ if (!game.user.isGM) {
+ return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmOnly'));
+ }
+
+ const { path, success } = event.currentTarget.dataset;
+ const { actor: actorData } = foundry.utils.getProperty(message.system, path);
+ const actor = game.actors.get(actorData._id);
+
+ if (!actor.testUserPermission(game.user, 'OWNER')) {
+ return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
+ }
+
+ const newMessageData = foundry.utils.deepClone(message.system);
+ foundry.utils.setProperty(newMessageData, `${path}.manualSuccess`, Boolean(success));
+ const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
+
+ const updatedContent = await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
+ { ...renderData, user: game.user }
+ );
+ const mess = game.messages.get(message._id);
+ await emitAsGM(
+ GMUpdateEvent.UpdateDocument,
+ mess.update.bind(mess),
+ {
+ ...renderData,
+ content: updatedContent
+ },
+ mess.uuid
+ );
+ }
+
+ async groupRollExpandSection(event) {
+ event.target
+ .closest('.group-roll-header-expand-section')
+ .querySelectorAll('i')
+ .forEach(element => {
+ element.classList.toggle('fa-angle-up');
+ element.classList.toggle('fa-angle-down');
+ });
+ event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed');
+ }
+
async riskItAllClearStressAndHitPoints(event, data) {
const resourceValue = event.target.dataset.resourceValue;
const actor = game.actors.get(event.target.dataset.actorId);
diff --git a/module/config/hooksConfig.mjs b/module/config/hooksConfig.mjs
index c0930d90..8d04be6d 100644
--- a/module/config/hooksConfig.mjs
+++ b/module/config/hooksConfig.mjs
@@ -1,6 +1,5 @@
export const hooksConfig = {
effectDisplayToggle: 'DHEffectDisplayToggle',
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
- tagTeamStart: 'DHTagTeamRollStart',
- groupRollStart: 'DHGroupRollStart'
+ tagTeamStart: 'DHTagTeamRollStart'
};
diff --git a/module/data/_module.mjs b/module/data/_module.mjs
index cd691ee1..0e7e295e 100644
--- a/module/data/_module.mjs
+++ b/module/data/_module.mjs
@@ -4,7 +4,6 @@ export { default as DhRollTable } from './rollTable.mjs';
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
export { default as TagTeamData } from './tagTeamData.mjs';
-export { default as GroupRollData } from './groupRollData.mjs';
export { default as SpotlightTracker } from './spotlightTracker.mjs';
export * as countdowns from './countdowns.mjs';
diff --git a/module/data/activeEffect/changeTypes/armor.mjs b/module/data/activeEffect/changeTypes/armor.mjs
index 217ff9dd..2f3b9765 100644
--- a/module/data/activeEffect/changeTypes/armor.mjs
+++ b/module/data/activeEffect/changeTypes/armor.mjs
@@ -82,7 +82,7 @@ export default class ArmorChange extends foundry.abstract.DataModel {
{
...change,
key: 'system.damageThresholds.major',
- type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
+ type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
priority: 50,
value: major
},
@@ -96,7 +96,7 @@ export default class ArmorChange extends foundry.abstract.DataModel {
{
...change,
key: 'system.damageThresholds.severe',
- type: CONFIG.DH.GENERAL.activeEffectModes.add.id,
+ type: CONFIG.DH.GENERAL.activeEffectModes.override.id,
priority: 50,
value: severe
},
diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs
index 52ead5ba..2878ad0c 100644
--- a/module/data/actor/character.mjs
+++ b/module/data/actor/character.mjs
@@ -736,22 +736,13 @@ export default class DhCharacter extends DhCreature {
}
}
- /* Armor and ArmorEffects can set a Base Damage Threshold. Characters only gain level*2 bonus to severe if this is not present */
- const severeThresholdMulitplier =
- this.armor ||
- this.parent.appliedEffects.some(x =>
- x.system.changes.some(x => x.type === 'armor' && x.value.damageThresholds)
- )
- ? 1
- : 2;
-
this.damageThresholds = {
major: this.armor
? this.armor.system.baseThresholds.major + this.levelData.level.current
: this.levelData.level.current,
severe: this.armor
? this.armor.system.baseThresholds.severe + this.levelData.level.current
- : this.levelData.level.current * severeThresholdMulitplier
+ : this.levelData.level.current * 2
};
const globalHopeMax = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxHope;
diff --git a/module/data/actor/party.mjs b/module/data/actor/party.mjs
index ec1beb99..2c797803 100644
--- a/module/data/actor/party.mjs
+++ b/module/data/actor/party.mjs
@@ -1,7 +1,6 @@
import BaseDataActor from './base.mjs';
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
import TagTeamData from '../tagTeamData.mjs';
-import GroupRollData from '../groupRollData.mjs';
export default class DhParty extends BaseDataActor {
/**@inheritdoc */
@@ -17,8 +16,7 @@ export default class DhParty extends BaseDataActor {
bags: new fields.NumberField({ initial: 0, integer: true }),
chests: new fields.NumberField({ initial: 0, integer: true })
}),
- tagTeam: new fields.EmbeddedDataField(TagTeamData),
- groupRoll: new fields.EmbeddedDataField(GroupRollData)
+ tagTeam: new fields.EmbeddedDataField(TagTeamData)
};
}
diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs
index 450d1ba2..c671de31 100644
--- a/module/data/chat-message/_modules.mjs
+++ b/module/data/chat-message/_modules.mjs
@@ -1,5 +1,6 @@
import DHAbilityUse from './abilityUse.mjs';
import DHActorRoll from './actorRoll.mjs';
+import DHGroupRoll from './groupRoll.mjs';
import DHSystemMessage from './systemMessage.mjs';
export const config = {
@@ -8,5 +9,6 @@ export const config = {
damageRoll: DHActorRoll,
dualityRoll: DHActorRoll,
fateRoll: DHActorRoll,
+ groupRoll: DHGroupRoll,
systemMessage: DHSystemMessage
};
diff --git a/module/data/chat-message/groupRoll.mjs b/module/data/chat-message/groupRoll.mjs
new file mode 100644
index 00000000..a5308323
--- /dev/null
+++ b/module/data/chat-message/groupRoll.mjs
@@ -0,0 +1,39 @@
+import { abilities } from '../../config/actorConfig.mjs';
+
+export default class DHGroupRoll extends foundry.abstract.TypeDataModel {
+ static defineSchema() {
+ const fields = foundry.data.fields;
+
+ return {
+ leader: new fields.EmbeddedDataField(GroupRollMemberField),
+ members: new fields.ArrayField(new fields.EmbeddedDataField(GroupRollMemberField))
+ };
+ }
+
+ get totalModifier() {
+ return this.members.reduce((acc, m) => {
+ if (m.manualSuccess === null) return acc;
+
+ return acc + (m.manualSuccess ? 1 : -1);
+ }, 0);
+ }
+}
+
+class GroupRollMemberField extends foundry.abstract.DataModel {
+ static defineSchema() {
+ const fields = foundry.data.fields;
+
+ return {
+ actor: new fields.ObjectField(),
+ trait: new fields.StringField({ choices: abilities }),
+ difficulty: new fields.StringField(),
+ result: new fields.ObjectField({ nullable: true, initial: null }),
+ manualSuccess: new fields.BooleanField({ nullable: true, initial: null })
+ };
+ }
+
+ /* Can be expanded if we handle automation of success/failure */
+ get success() {
+ return manualSuccess;
+ }
+}
diff --git a/module/data/groupRollData.mjs b/module/data/groupRollData.mjs
deleted file mode 100644
index 78a06b13..00000000
--- a/module/data/groupRollData.mjs
+++ /dev/null
@@ -1,40 +0,0 @@
-export default class GroupRollData extends foundry.abstract.DataModel {
- static defineSchema() {
- const fields = foundry.data.fields;
-
- return {
- leader: new fields.EmbeddedDataField(CharacterData, { nullable: true, initial: null }),
- aidingCharacters: new fields.TypedObjectField(new fields.EmbeddedDataField(CharacterData))
- };
- }
-
- get participants() {
- return {
- ...(this.leader ? { [this.leader.id]: this.leader } : {}),
- ...this.aidingCharacters
- };
- }
-}
-
-export class CharacterData extends foundry.abstract.DataModel {
- static defineSchema() {
- const fields = foundry.data.fields;
-
- return {
- id: new fields.StringField({ required: true }),
- name: new fields.StringField({ required: true }),
- img: new fields.StringField({ required: true }),
- rollChoice: new fields.StringField({
- choices: CONFIG.DH.ACTOR.abilities,
- initial: CONFIG.DH.ACTOR.abilities.agility.id
- }),
- rollData: new fields.JSONField({ nullable: true, initial: null }),
- selected: new fields.BooleanField({ initial: false }),
- successfull: new fields.BooleanField({ nullable: true, initial: null })
- };
- }
-
- get roll() {
- return this.rollData ? CONFIG.Dice.daggerheart.DualityRoll.fromData(this.rollData) : null;
- }
-}
diff --git a/module/systemRegistration/migrations.mjs b/module/systemRegistration/migrations.mjs
index b4c446b2..c2c53f4e 100644
--- a/module/systemRegistration/migrations.mjs
+++ b/module/systemRegistration/migrations.mjs
@@ -343,7 +343,7 @@ export async function runMigrations() {
lastMigrationVersion = '2.0.0';
}
- if (foundry.utils.isNewerVersion('2.1.0', lastMigrationVersion)) {
+ if (foundry.utils.isNewerVersion('2.0.4', lastMigrationVersion)) {
const downtimeMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew);
if (downtimeMoves.restMoves.longRest.moves.repairArmor) {
await downtimeMoves.updateSource({
@@ -352,7 +352,7 @@ export async function runMigrations() {
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, downtimeMoves.toObject());
}
- lastMigrationVersion = '2.1.0';
+ lastMigrationVersion = '2.0.4';
}
//#endregion
diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs
index 8fed346d..fb152959 100644
--- a/module/systemRegistration/socket.mjs
+++ b/module/systemRegistration/socket.mjs
@@ -18,8 +18,6 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
case socketEvent.TagTeamStart:
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
break;
- case socketEvent.GroupRollStart:
- Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, data);
}
}
@@ -28,8 +26,7 @@ export const socketEvent = {
Refresh: 'DhRefresh',
DhpFearUpdate: 'DhFearUpdate',
DowntimeTrigger: 'DowntimeTrigger',
- TagTeamStart: 'DhTagTeamStart',
- GroupRollStart: 'DhGroupRollStart'
+ TagTeamStart: 'DhTagTeamStart'
};
export const GMUpdateEvent = {
@@ -44,7 +41,6 @@ export const GMUpdateEvent = {
export const RefreshType = {
Countdown: 'DhCoundownRefresh',
TagTeamRoll: 'DhTagTeamRollRefresh',
- GroupRoll: 'DhGroupRollRefresh',
EffectsDisplay: 'DhEffectsDisplayRefresh',
Scene: 'DhSceneRefresh',
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
diff --git a/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json b/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json
index 7956d6eb..098f5f4c 100644
--- a/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json
+++ b/src/packs/domains/domainCard_Bare_Bones_l5D9kq901JDESaXw.json
@@ -4,7 +4,7 @@
"type": "domainCard",
"folder": "QpOL7jPbMBzH96qR",
"system": {
- "description": "When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:
Tier 1: 9/19
Tier 2: 11/24
Tier 3: 13/31
Tier 4: 15/38
",
+ "description": "When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:
Tier 1: 9/19Tier 2: 11/24Tier 3: 13/31Tier 4: 15/38Equip the below armor to use Bare Bones.
@UUID[Compendium.daggerheart.armors.Item.ITAjcigTcUw5pMCN]{Bare Bones}
",
"domain": "valor",
"recallCost": 0,
"level": 1,
@@ -28,33 +28,22 @@
{
"type": "armor",
"phase": "initial",
+ "priority": 20,
"value": {
- "current": 0,
"max": "3 + @system.traits.strength.value",
"interaction": "inactive",
"damageThresholds": {
- "major": "9 + (@tier - 1) * 2",
+ "major": "9 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )",
"severe": "19 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )"
}
- },
- "priority": 20
+ }
}
- ],
- "duration": {
- "type": ""
- }
+ ]
},
"_id": "FCsgz7Tdsw6QUzBs",
"img": "icons/magic/control/buff-strength-muscle-damage-orange.webp",
"disabled": false,
- "start": {
- "time": 0,
- "combat": null,
- "combatant": null,
- "initiative": null,
- "round": null,
- "turn": null
- },
+ "start": null,
"duration": {
"value": null,
"units": "seconds",
diff --git a/styles/less/dialog/group-roll-dialog/initialization.less b/styles/less/dialog/group-roll-dialog/initialization.less
deleted file mode 100644
index b2e7e021..00000000
--- a/styles/less/dialog/group-roll-dialog/initialization.less
+++ /dev/null
@@ -1,79 +0,0 @@
-.theme-light .daggerheart.dialog.dh-style.views.group-roll-dialog {
- .initialization-container .members-container .member-container {
- .member-name {
- background-image: url('../assets/parchments/dh-parchment-light.png');
- }
- }
-}
-
-.daggerheart.dialog.dh-style.views.group-roll-dialog {
- .initialization-container {
- h2 {
- text-align: center;
- }
-
- .members-container {
- display: grid;
- grid-template-columns: 1fr 1fr 1fr 1fr;
- gap: 8px;
-
- .member-container {
- position: relative;
- display: flex;
- justify-content: center;
-
- &.inactive {
- opacity: 0.4;
- }
-
- .member-name {
- position: absolute;
- padding: 0 2px;
- border: 1px solid;
- border-radius: 6px;
- margin-top: 4px;
- color: light-dark(@dark, @beige);
- background-image: url('../assets/parchments/dh-parchment-dark.png');
- text-align: center;
- }
-
- img {
- border-radius: 6px;
- border: 1px solid light-dark(@dark-blue, @golden);
- }
- }
- }
-
- .main-roll {
- margin-top: 8px;
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 8px;
-
- &.inactive {
- opacity: 0.4;
- }
- }
-
- footer {
- margin-top: 8px;
- display: flex;
- gap: 8px;
-
- button {
- flex: 1;
- }
-
- .finish-tools {
- flex: none;
- display: flex;
- align-items: center;
- gap: 4px;
-
- &.inactive {
- opacity: 0.4;
- }
- }
- }
- }
-}
diff --git a/styles/less/dialog/group-roll-dialog/leader.less b/styles/less/dialog/group-roll-dialog/leader.less
deleted file mode 100644
index b3fa3a3b..00000000
--- a/styles/less/dialog/group-roll-dialog/leader.less
+++ /dev/null
@@ -1,35 +0,0 @@
-.daggerheart.dialog.dh-style.views.group-roll-dialog {
- .main-character-outer-container {
- &.inactive {
- opacity: 0.3;
- pointer-events: none;
- }
-
- .main-character-container {
- .character-info {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- height: 64px;
-
- img {
- height: 64px;
- border-radius: 6px;
- border: 1px solid light-dark(@dark-blue, @golden);
- }
-
- .character-data {
- padding-left: 0.75rem;
- flex: 1;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- text-align: left;
- font-size: var(--font-size-18);
- }
- }
- }
- }
-}
diff --git a/styles/less/dialog/group-roll-dialog/sheet.less b/styles/less/dialog/group-roll-dialog/sheet.less
deleted file mode 100644
index 70afc1fe..00000000
--- a/styles/less/dialog/group-roll-dialog/sheet.less
+++ /dev/null
@@ -1,265 +0,0 @@
-.daggerheart.dialog.dh-style.views.group-roll-dialog {
- .team-container {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 16px;
- margin-bottom: 16px;
-
- .team-member-container {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- gap: 8px;
- flex: 1;
-
- &.inactive {
- opacity: 0.3;
- pointer-events: none;
- }
-
- .data-container {
- display: flex;
- flex-direction: column;
- gap: 8px;
- width: 100%;
- }
-
- .member-info {
- display: flex;
- align-items: start;
- width: 100%;
-
- img {
- height: 64px;
- border-radius: 6px;
- border: 1px solid light-dark(@dark-blue, @golden);
- }
-
- .member-data {
- padding-left: 0.75rem;
- flex: 1;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- text-align: left;
- font-size: var(--font-size-18);
- }
- }
- }
- }
-
- .roll-container {
- display: flex;
- flex-direction: column;
- }
-
- .roll-title {
- font-size: var(--font-size-20);
- font-weight: bold;
- color: light-dark(@dark-blue, @golden);
- text-align: center;
- display: flex;
- align-items: center;
- gap: 8px;
-
- &.hope,
- &.fear,
- &.critical {
- color: var(--text-color);
- }
-
- &.hope {
- --text-color: @golden;
- }
-
- &.fear {
- --text-color: @chat-blue;
- }
-
- &.critical {
- --text-color: @chat-purple;
- }
-
- &::before,
- &::after {
- color: var(--text-color);
- content: '';
- flex: 1;
- height: 2px;
- }
-
- &::before {
- background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%);
- }
-
- &::after {
- background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%);
- }
- }
-
- .roll-tools {
- display: flex;
- gap: 4px;
- align-items: center;
-
- img {
- height: 16px;
- }
-
- a {
- display: flex;
- font-size: 16px;
-
- &:hover {
- text-shadow: none;
- filter: drop-shadow(0 0 8px var(--golden));
- }
- }
- }
-
- .roll-data {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 4px;
-
- &.hope {
- --text-color: @golden;
- --bg-color: @golden-40;
- }
-
- &.fear {
- --text-color: @chat-blue;
- --bg-color: @chat-blue-40;
- }
-
- &.critical {
- --text-color: @chat-purple;
- --bg-color: @chat-purple-40;
- }
-
- .duality-label {
- color: var(--text-color);
- font-size: var(--font-size-20);
- font-weight: bold;
- text-align: center;
-
- .unused-damage {
- text-decoration: line-through;
- }
- }
-
- .roll-dice-container {
- display: flex;
- align-items: center;
- justify-content: center;
- flex-wrap: wrap;
- gap: 8px;
-
- .roll-dice {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
-
- .dice-label {
- position: absolute;
- color: white;
- font-size: 1rem;
- paint-order: stroke fill;
- -webkit-text-stroke: 2px black;
- }
-
- img {
- height: 32px;
- }
- }
-
- .roll-operator {
- font-size: var(--font-size-24);
- }
-
- .roll-value {
- font-size: 18px;
- }
- }
-
- .roll-total {
- background: var(--bg-color);
- color: var(--text-color);
- border-radius: 4px;
- padding: 3px;
- }
- }
-
- .roll-success-container {
- display: flex;
- align-items: center;
- justify-content: space-around;
-
- .roll-success-tools {
- display: flex;
- align-items: center;
- gap: 4px;
- color: light-dark(@dark-blue, @golden);
-
- i {
- font-size: 24px;
- }
- }
-
- .roll-success-modifier {
- display: flex;
- align-items: center;
- justify-content: right;
- gap: 2px;
- font-size: var(--font-size-20);
- padding: 0px 4px;
-
- &.success {
- background: @green-10;
- color: @green;
- }
-
- &.failure {
- background: @red-10;
- color: @red;
- }
- }
- }
-
- .section-title {
- font-size: var(--font-size-18);
- font-weight: bold;
- }
-
- .group-roll-results {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 4px;
- font-size: var(--font-size-20);
-
- .group-roll-container {
- display: flex;
- align-items: center;
- gap: 2px;
- }
- }
-
- .finish-container {
- margin-top: 16px;
- gap: 16px;
- display: grid;
- grid-template-columns: 1fr 1fr 1fr;
-
- .finish-button {
- grid-column: span 2;
- }
- }
-
- .hint {
- text-align: center;
- }
-}
diff --git a/styles/less/dialog/index.less b/styles/less/dialog/index.less
index 947142ff..73738eaa 100644
--- a/styles/less/dialog/index.less
+++ b/styles/less/dialog/index.less
@@ -36,10 +36,6 @@
@import './tag-team-dialog/initialization.less';
@import './tag-team-dialog/sheet.less';
-@import './group-roll-dialog/initialization.less';
-@import './group-roll-dialog/leader.less';
-@import './group-roll-dialog/sheet.less';
-
@import './image-select/sheet.less';
@import './item-transfer/sheet.less';
diff --git a/styles/less/dialog/tag-team-dialog/initialization.less b/styles/less/dialog/tag-team-dialog/initialization.less
index 8557d231..30676f82 100644
--- a/styles/less/dialog/tag-team-dialog/initialization.less
+++ b/styles/less/dialog/tag-team-dialog/initialization.less
@@ -1,11 +1,3 @@
-.theme-light .daggerheart.dialog.dh-style.views.tag-team-dialog {
- .initialization-container .members-container .member-container {
- .member-name {
- background-image: url('../assets/parchments/dh-parchment-light.png');
- }
- }
-}
-
.daggerheart.dialog.dh-style.views.tag-team-dialog {
.initialization-container {
h2 {
@@ -28,18 +20,6 @@
.member-name {
position: absolute;
- padding: 0 2px;
- border: 1px solid;
- border-radius: 6px;
- margin-top: 4px;
- color: light-dark(@dark, @beige);
- background-image: url('../assets/parchments/dh-parchment-dark.png');
- text-align: center;
- }
-
- img {
- border-radius: 6px;
- border: 1px solid light-dark(@dark-blue, @golden);
}
}
}
diff --git a/system.json b/system.json
index 450c33b2..300b1042 100644
--- a/system.json
+++ b/system.json
@@ -2,9 +2,9 @@
"id": "daggerheart",
"title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system",
- "version": "2.1.1",
+ "version": "2.0.3",
"compatibility": {
- "minimum": "14.359",
+ "minimum": "14.360",
"verified": "14.360",
"maximum": "14"
},
@@ -290,6 +290,7 @@
"damageRoll": {},
"abilityUse": {},
"tagTeam": {},
+ "groupRoll": {},
"systemMessage": {}
}
},
@@ -298,5 +299,5 @@
"secondaryTokenAttribute": "resources.stress",
"url": "https://github.com/Foundryborne/daggerheart",
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/main/system.json",
- "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.1.1/system.zip"
+ "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.0.3/system.zip"
}
diff --git a/templates/dialogs/groupRollDialog/footer.hbs b/templates/dialogs/groupRollDialog/footer.hbs
deleted file mode 100644
index cb041247..00000000
--- a/templates/dialogs/groupRollDialog/footer.hbs
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- {{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelGroupRoll"}}
- {{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.finishGroupRoll"}}
-
-
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/groupRoll.hbs b/templates/dialogs/groupRollDialog/groupRoll.hbs
deleted file mode 100644
index edf1c980..00000000
--- a/templates/dialogs/groupRollDialog/groupRoll.hbs
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
- {{localize "DAGGERHEART.GENERAL.result.single"}}
-
-
- {{#if hasRolled}}
{{groupRoll.total}} {{groupRoll.totalLabel}} {{/if}}
-
- {{#if groupRoll.leaderTotal includeZero=true}}{{groupRoll.leaderTotal}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leaderRoll"}}{{/if}}
- {{#each groupRoll.modifiers as |modifier|}}
- {{#if (gte modifier 0)}}+{{else}}-{{/if}}
- {{positive modifier}}
- {{/each}}
- {{#unless groupRoll.modifiers.length}}
- +
- {{localize "DAGGERHEART.GENERAL.Modifier.plural"}}
- {{/unless}}
-
-
-
-
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/groupRollMember.hbs b/templates/dialogs/groupRollDialog/groupRollMember.hbs
deleted file mode 100644
index acf8e8f1..00000000
--- a/templates/dialogs/groupRollDialog/groupRollMember.hbs
+++ /dev/null
@@ -1,85 +0,0 @@
-{{#with (lookup members partId)}}
-
-
-
-
-
-
- {{#if readyToRoll}}
-
-
- {{localize "DAGGERHEART.GENERAL.roll"}}
-
-
-
- {{#if roll}}
-
-
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
-
-
- {{roll.dHope.total}}
-
-
-
+
-
- {{roll.dFear.total}}
-
-
- {{#if roll.advantage.type}}
-
{{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}}
-
- {{roll.advantage.value}}
-
-
- {{/if}}
-
{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}
-
{{positive roll.modifierTotal}}
-
-
- {{else}}
-
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}
- {{/if}}
-
- {{/if}}
- {{#if hasRolled}}
-
- {{#if ../isGM}}
-
- {{/if}}
-
- {{localize "DAGGERHEART.GENERAL.Modifier.single"}}{{#if successfull}} + 1{{else if (isNullish successfull)}} + ?{{else}} - 1{{/if}}
-
-
- {{/if}}
-
-
-{{/with}}
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/initialization.hbs b/templates/dialogs/groupRollDialog/initialization.hbs
deleted file mode 100644
index a520b8bd..00000000
--- a/templates/dialogs/groupRollDialog/initialization.hbs
+++ /dev/null
@@ -1,32 +0,0 @@
-
\ No newline at end of file
diff --git a/templates/dialogs/groupRollDialog/leader.hbs b/templates/dialogs/groupRollDialog/leader.hbs
deleted file mode 100644
index 3d5db3f7..00000000
--- a/templates/dialogs/groupRollDialog/leader.hbs
+++ /dev/null
@@ -1,73 +0,0 @@
-
- {{#with leader}}
-
-
{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leader"}}
-
-
-
-
-
-
-
-
- {{#if readyToRoll}}
-
-
- {{localize "DAGGERHEART.GENERAL.roll"}}
-
-
-
- {{#if roll}}
-
-
{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}
-
-
- {{roll.dHope.total}}
-
-
-
+
-
- {{roll.dFear.total}}
-
-
- {{#if roll.advantage.type}}
-
{{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}}
-
- {{roll.advantage.value}}
-
-
- {{/if}}
-
{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}
-
{{positive roll.modifierTotal}}
-
-
- {{else}}
-
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}
- {{/if}}
-
- {{/if}}
-
-
- {{/with}}
-
\ No newline at end of file
diff --git a/templates/dialogs/tagTeamDialog/initialization.hbs b/templates/dialogs/tagTeamDialog/initialization.hbs
index 7ccdf566..d25e8f6c 100644
--- a/templates/dialogs/tagTeamDialog/initialization.hbs
+++ b/templates/dialogs/tagTeamDialog/initialization.hbs
@@ -1,4 +1,5 @@
+ {{partId}}
{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}
{{#each memberSelection as |member|}}
diff --git a/templates/sheets/actors/party/party-members.hbs b/templates/sheets/actors/party/party-members.hbs
index bc0c6672..8a113ac8 100644
--- a/templates/sheets/actors/party/party-members.hbs
+++ b/templates/sheets/actors/party/party-members.hbs
@@ -9,10 +9,15 @@
Tag Team Roll
-
+
Group Roll
+ {{!-- NOT YET IMPLEMENTED --}}
+ {{!--
+
+ Help Action
+ --}}
{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}