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:

", + "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:

Equip 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 @@ -
-
- - -
-
\ 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)}} -
-
-
- -
- {{name}} -
-
-
- {{!-- --}} - -
-
-
-
-
- {{#if readyToRoll}} -
- - {{localize "DAGGERHEART.GENERAL.roll"}} -
- - - - - {{#if hasRolled}} - - - - {{/if}} -
-
- - {{#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 @@ -
-
- {{#each memberSelection as |member|}} - - {{member.name}} - - - {{/each}} -
- -
-
- -
- -
-
-
- - -
\ 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"}}
-
-
-
- -
- {{name}} -
-
-
- -
-
-
-
-
-
- - {{#if readyToRoll}} -
- - {{localize "DAGGERHEART.GENERAL.roll"}} -
- - - - - {{#if hasRolled}} - - - - {{/if}} -
-
- - {{#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 - + {{!-- NOT YET IMPLEMENTED --}} + {{!-- --}}