mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
[Feature] Group Roll Rework (#1785)
* Initial * . * Improvements * . * Renamed 'Main Charater' to 'Leader' * Localization fixes * . * Fixed roll sound coming when canceling a roll. Fixed the leader PART not being disabled when the player isn't the leader
This commit is contained in:
parent
97636fa134
commit
a897037dc4
27 changed files with 1214 additions and 455 deletions
|
|
@ -343,6 +343,17 @@ 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 updateActorsRangeDependentEffects = async token => {
|
||||||
const rangeMeasurement = game.settings.get(
|
const rangeMeasurement = game.settings.get(
|
||||||
CONFIG.DH.id,
|
CONFIG.DH.id,
|
||||||
|
|
|
||||||
23
lang/en.json
23
lang/en.json
|
|
@ -733,6 +733,17 @@
|
||||||
"selectRoll": "Select which roll value to be used for the Tag Team"
|
"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": {
|
"TokenConfig": {
|
||||||
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
"actorSizeUsed": "Actor size is set, determining the dimensions"
|
||||||
}
|
}
|
||||||
|
|
@ -2982,18 +2993,6 @@
|
||||||
"immunityTo": "Immunity: {immunities}"
|
"immunityTo": "Immunity: {immunities}"
|
||||||
},
|
},
|
||||||
"featureTitle": "Class Feature",
|
"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": {
|
"healingRoll": {
|
||||||
"title": "Heal - {damage}",
|
"title": "Heal - {damage}",
|
||||||
"heal": "Heal",
|
"heal": "Heal",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.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 TagTeamDialog } from './tagTeamDialog.mjs';
|
||||||
|
export { default as GroupRollDialog } from './groupRollDialog.mjs';
|
||||||
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
export { default as RiskItAllDialog } from './riskItAllDialog.mjs';
|
||||||
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettingsDialog } from './CompendiumBrowserSettings.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
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 ? `<strong>${matchText}</strong>` : ''}${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 ? `<strong>${matchText}</strong>` : ''}${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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
527
module/applications/dialogs/groupRollDialog.mjs
Normal file
527
module/applications/dialogs/groupRollDialog.mjs
Normal file
|
|
@ -0,0 +1,527 @@
|
||||||
|
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', '<div class="team-container"></div>');
|
||||||
|
initializationPart.insertAdjacentHTML(
|
||||||
|
'afterend',
|
||||||
|
`<div class="section-title">${game.i18n.localize('Aiding Characters')}</div>`
|
||||||
|
);
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||||
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
|
||||||
import DhpActor from '../../../documents/actor.mjs';
|
import DhpActor from '../../../documents/actor.mjs';
|
||||||
|
|
||||||
export default class Party extends DHBaseActorSheet {
|
export default class Party extends DHBaseActorSheet {
|
||||||
|
|
@ -117,6 +116,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
relativeTo: this.document
|
relativeTo: this.document
|
||||||
});
|
});
|
||||||
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
context.tagTeamActive = Boolean(this.document.system.tagTeam.initiator);
|
||||||
|
context.groupRollActive = Boolean(this.document.system.groupRoll.leader);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareMembersContext(context, _options) {
|
async _prepareMembersContext(context, _options) {
|
||||||
|
|
@ -318,9 +318,7 @@ export default class Party extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async #groupRoll(_params) {
|
static async #groupRoll(_params) {
|
||||||
new GroupRollDialog(
|
new game.system.api.applications.dialogs.GroupRollDialog(this.document).render({ force: true });
|
||||||
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
|
|
||||||
).render({ force: true });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { abilities } from '../../config/actorConfig.mjs';
|
|
||||||
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
import { enrichedDualityRoll } from '../../enrichers/DualityRollEnricher.mjs';
|
||||||
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
import { enrichedFateRoll, getFateTypeData } from '../../enrichers/FateRollEnricher.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from '../../helpers/utils.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 {
|
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
|
@ -150,18 +148,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
html.querySelectorAll('.reroll-button').forEach(element =>
|
html.querySelectorAll('.reroll-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.rerollEvent(event, message))
|
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 =>
|
html.querySelectorAll('.risk-it-all-button').forEach(element =>
|
||||||
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
element.addEventListener('click', event => this.riskItAllClearStressAndHitPoints(event, data))
|
||||||
);
|
);
|
||||||
|
|
@ -305,174 +291,6 @@ 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) {
|
async riskItAllClearStressAndHitPoints(event, data) {
|
||||||
const resourceValue = event.target.dataset.resourceValue;
|
const resourceValue = event.target.dataset.resourceValue;
|
||||||
const actor = game.actors.get(event.target.dataset.actorId);
|
const actor = game.actors.get(event.target.dataset.actorId);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export const hooksConfig = {
|
export const hooksConfig = {
|
||||||
effectDisplayToggle: 'DHEffectDisplayToggle',
|
effectDisplayToggle: 'DHEffectDisplayToggle',
|
||||||
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
lockedTooltipDismissed: 'DHLockedTooltipDismissed',
|
||||||
tagTeamStart: 'DHTagTeamRollStart'
|
tagTeamStart: 'DHTagTeamRollStart',
|
||||||
|
groupRollStart: 'DHGroupRollStart'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs';
|
||||||
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
export { default as RegisteredTriggers } from './registeredTriggers.mjs';
|
||||||
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
|
||||||
export { default as TagTeamData } from './tagTeamData.mjs';
|
export { default as TagTeamData } from './tagTeamData.mjs';
|
||||||
|
export { default as GroupRollData } from './groupRollData.mjs';
|
||||||
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
export { default as SpotlightTracker } from './spotlightTracker.mjs';
|
||||||
|
|
||||||
export * as countdowns from './countdowns.mjs';
|
export * as countdowns from './countdowns.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
|
||||||
import TagTeamData from '../tagTeamData.mjs';
|
import TagTeamData from '../tagTeamData.mjs';
|
||||||
|
import GroupRollData from '../groupRollData.mjs';
|
||||||
|
|
||||||
export default class DhParty extends BaseDataActor {
|
export default class DhParty extends BaseDataActor {
|
||||||
/**@inheritdoc */
|
/**@inheritdoc */
|
||||||
|
|
@ -16,7 +17,8 @@ export default class DhParty extends BaseDataActor {
|
||||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||||
}),
|
}),
|
||||||
tagTeam: new fields.EmbeddedDataField(TagTeamData)
|
tagTeam: new fields.EmbeddedDataField(TagTeamData),
|
||||||
|
groupRoll: new fields.EmbeddedDataField(GroupRollData)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import DHAbilityUse from './abilityUse.mjs';
|
import DHAbilityUse from './abilityUse.mjs';
|
||||||
import DHActorRoll from './actorRoll.mjs';
|
import DHActorRoll from './actorRoll.mjs';
|
||||||
import DHGroupRoll from './groupRoll.mjs';
|
|
||||||
import DHSystemMessage from './systemMessage.mjs';
|
import DHSystemMessage from './systemMessage.mjs';
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|
@ -9,6 +8,5 @@ export const config = {
|
||||||
damageRoll: DHActorRoll,
|
damageRoll: DHActorRoll,
|
||||||
dualityRoll: DHActorRoll,
|
dualityRoll: DHActorRoll,
|
||||||
fateRoll: DHActorRoll,
|
fateRoll: DHActorRoll,
|
||||||
groupRoll: DHGroupRoll,
|
|
||||||
systemMessage: DHSystemMessage
|
systemMessage: DHSystemMessage
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
module/data/groupRollData.mjs
Normal file
40
module/data/groupRollData.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,8 @@ export function handleSocketEvent({ action = null, data = {} } = {}) {
|
||||||
case socketEvent.TagTeamStart:
|
case socketEvent.TagTeamStart:
|
||||||
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, data);
|
||||||
break;
|
break;
|
||||||
|
case socketEvent.GroupRollStart:
|
||||||
|
Hooks.callAll(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +28,8 @@ export const socketEvent = {
|
||||||
Refresh: 'DhRefresh',
|
Refresh: 'DhRefresh',
|
||||||
DhpFearUpdate: 'DhFearUpdate',
|
DhpFearUpdate: 'DhFearUpdate',
|
||||||
DowntimeTrigger: 'DowntimeTrigger',
|
DowntimeTrigger: 'DowntimeTrigger',
|
||||||
TagTeamStart: 'DhTagTeamStart'
|
TagTeamStart: 'DhTagTeamStart',
|
||||||
|
GroupRollStart: 'DhGroupRollStart'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GMUpdateEvent = {
|
export const GMUpdateEvent = {
|
||||||
|
|
@ -41,6 +44,7 @@ export const GMUpdateEvent = {
|
||||||
export const RefreshType = {
|
export const RefreshType = {
|
||||||
Countdown: 'DhCoundownRefresh',
|
Countdown: 'DhCoundownRefresh',
|
||||||
TagTeamRoll: 'DhTagTeamRollRefresh',
|
TagTeamRoll: 'DhTagTeamRollRefresh',
|
||||||
|
GroupRoll: 'DhGroupRollRefresh',
|
||||||
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
EffectsDisplay: 'DhEffectsDisplayRefresh',
|
||||||
Scene: 'DhSceneRefresh',
|
Scene: 'DhSceneRefresh',
|
||||||
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
|
CompendiumBrowser: 'DhCompendiumBrowserRefresh'
|
||||||
|
|
|
||||||
78
styles/less/dialog/group-roll-dialog/initialization.less
Normal file
78
styles/less/dialog/group-roll-dialog/initialization.less
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
.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');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
styles/less/dialog/group-roll-dialog/leader.less
Normal file
35
styles/less/dialog/group-roll-dialog/leader.less
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
266
styles/less/dialog/group-roll-dialog/sheet.less
Normal file
266
styles/less/dialog/group-roll-dialog/sheet.less
Normal file
|
|
@ -0,0 +1,266 @@
|
||||||
|
.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: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 64px;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,10 @@
|
||||||
@import './tag-team-dialog/initialization.less';
|
@import './tag-team-dialog/initialization.less';
|
||||||
@import './tag-team-dialog/sheet.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 './image-select/sheet.less';
|
||||||
|
|
||||||
@import './item-transfer/sheet.less';
|
@import './item-transfer/sheet.less';
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,17 @@
|
||||||
|
|
||||||
.member-name {
|
.member-name {
|
||||||
position: absolute;
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid light-dark(@dark-blue, @golden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -290,7 +290,6 @@
|
||||||
"damageRoll": {},
|
"damageRoll": {},
|
||||||
"abilityUse": {},
|
"abilityUse": {},
|
||||||
"tagTeam": {},
|
"tagTeam": {},
|
||||||
"groupRoll": {},
|
|
||||||
"systemMessage": {}
|
"systemMessage": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
6
templates/dialogs/groupRollDialog/footer.hbs
Normal file
6
templates/dialogs/groupRollDialog/footer.hbs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<section class="tab {{#if tabs.groupRoll.active}} active{{/if}}" data-group="{{tabs.groupRoll.group}}" data-tab="{{tabs.groupRoll.id}}">
|
||||||
|
<div class="finish-container">
|
||||||
|
<button type="button" data-action="cancelRoll">{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.cancelGroupRoll"}}</button>
|
||||||
|
<button type="button" data-action="finishRoll" {{#unless canFinishRoll}}disabled{{/unless}} class="finish-button">{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.finishGroupRoll"}}</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
20
templates/dialogs/groupRollDialog/groupRoll.hbs
Normal file
20
templates/dialogs/groupRollDialog/groupRoll.hbs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<section class="tab {{#if tabs.groupRoll.active}} active{{/if}}" data-group="{{tabs.groupRoll.group}}" data-tab="{{tabs.groupRoll.id}}">
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.result.single"}}</legend>
|
||||||
|
|
||||||
|
<div class="group-roll-results">
|
||||||
|
{{#if hasRolled}}<span class="roll-title {{groupRoll.totalDualityClass}}">{{groupRoll.total}} {{groupRoll.totalLabel}}</span>{{/if}}
|
||||||
|
<div class="group-roll-container">
|
||||||
|
<span>{{#if groupRoll.leaderTotal includeZero=true}}{{groupRoll.leaderTotal}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leaderRoll"}}{{/if}}</span>
|
||||||
|
{{#each groupRoll.modifiers as |modifier|}}
|
||||||
|
<span>{{#if (gte modifier 0)}}+{{else}}-{{/if}}</span>
|
||||||
|
<span>{{positive modifier}}</span>
|
||||||
|
{{/each}}
|
||||||
|
{{#unless groupRoll.modifiers.length}}
|
||||||
|
<span>+</span>
|
||||||
|
<span>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</span>
|
||||||
|
{{/unless}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
85
templates/dialogs/groupRollDialog/groupRollMember.hbs
Normal file
85
templates/dialogs/groupRollDialog/groupRollMember.hbs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
{{#with (lookup members partId)}}
|
||||||
|
<fieldset class="team-member-container {{#if @root.allHaveRolled}}select-padding{{/if}} {{#unless isEditable}}inactive{{/unless}}" data-member-key="{{@root.partId}}">
|
||||||
|
<div class="data-container">
|
||||||
|
<div class="member-info">
|
||||||
|
<img src="{{img}}" />
|
||||||
|
<div class="member-data">
|
||||||
|
<span class="member-name">{{name}}</span>
|
||||||
|
<div class="roll-setup">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-fields">
|
||||||
|
{{!-- <label>{{localize "DAGGERHEART.GENERAL.Trait.single"}}</label> --}}
|
||||||
|
<select name="{{concat "system.groupRoll.aidingCharacters." @root.partId ".rollChoice"}}" {{#if hasRolled}}disabled{{/if}}>
|
||||||
|
{{selectOptions ../traitOptions selected=rollChoice localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{#if readyToRoll}}
|
||||||
|
<div class="roll-container">
|
||||||
|
<span class="roll-title">
|
||||||
|
{{localize "DAGGERHEART.GENERAL.roll"}}
|
||||||
|
<div class="roll-tools">
|
||||||
|
<a class="roll-button" data-action="makeRoll" data-member="{{@root.partId}}">
|
||||||
|
<img src="systems/daggerheart/assets/icons/dice/hope/d12.svg" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{{#if hasRolled}}
|
||||||
|
<a class="delete-button" data-action="removeRoll" data-member="{{@root.partId}}">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{#if roll}}
|
||||||
|
<div class="roll-data {{#if roll.withHope}}hope{{else if roll.withFear}}fear{{else}}critical{{/if}}">
|
||||||
|
<div class="duality-label">{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}</div>
|
||||||
|
<div class="roll-dice-container">
|
||||||
|
<a class="roll-dice" data-action="rerollDice" data-member="{{@root.partId}}" data-dice-type="hope">
|
||||||
|
<span class="dice-label">{{roll.dHope.total}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" roll.dHope.denomination ".svg"}}" />
|
||||||
|
</a>
|
||||||
|
<span class="roll-operator">+</span>
|
||||||
|
<a class="roll-dice" data-action="rerollDice" data-member="{{@root.partId}}" data-dice-type="fear">
|
||||||
|
<span class="dice-label">{{roll.dFear.total}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/fear/" roll.dFear.denomination ".svg"}}" />
|
||||||
|
</a>
|
||||||
|
{{#if roll.advantage.type}}
|
||||||
|
<span class="roll-operator">{{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}}</span>
|
||||||
|
<span class="roll-dice">
|
||||||
|
<span class="dice-label">{{roll.advantage.value}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/" (ifThen (eq roll.advantage.type 1) "adv/" "disadv/") roll.advantage.dice ".svg"}}" />
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
<span class="roll-operator">{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}</span>
|
||||||
|
<span class="roll-value">{{positive roll.modifierTotal}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="hint">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
{{#if hasRolled}}
|
||||||
|
<div class="roll-success-container">
|
||||||
|
{{#if ../isGM}}
|
||||||
|
<div class="roll-success-tools">
|
||||||
|
<a data-action="markSuccessfull" data-member="{{@root.partId}}" data-successfull="true">
|
||||||
|
<i class="{{#if successfull}}fa-solid{{else}}fa-regular{{/if}} fa-circle-check"></i>
|
||||||
|
</a>
|
||||||
|
<a data-action="markSuccessfull" data-member="{{@root.partId}}">
|
||||||
|
<i class="{{#unless successfull}}fa-solid{{else}}fa-regular{{/unless}} fa-circle-xmark"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
<div class="roll-success-modifier {{#if successfull}}success{{else if (not (isNullish successfull))}}failure{{/if}}">
|
||||||
|
{{localize "DAGGERHEART.GENERAL.Modifier.single"}}{{#if successfull}} + 1{{else if (isNullish successfull)}} + ?{{else}} - 1{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/with}}
|
||||||
32
templates/dialogs/groupRollDialog/initialization.hbs
Normal file
32
templates/dialogs/groupRollDialog/initialization.hbs
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<section class="initialization-container tab {{#if tabs.initialization.active}} active{{/if}}" data-group="{{tabs.initialization.group}}" data-tab="{{tabs.initialization.id}}">
|
||||||
|
<div class="members-container">
|
||||||
|
{{#each memberSelection as |member|}}
|
||||||
|
<a
|
||||||
|
class="member-container {{#unless member.selected}}inactive {{#if ../allselected}}locked{{/if}}{{/unless}}"
|
||||||
|
data-action="toggleSelectMember" data-id="{{member.id}}" {{#if (and (not member.selected) ../allSelected)}}disabled{{/if}}
|
||||||
|
>
|
||||||
|
<span class="member-name">{{member.name}}</span>
|
||||||
|
<img src="{{member.img}}" />
|
||||||
|
</a>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-roll {{#if selectedLeaderDisabled}}inactive{{/if}}">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leader"}}</label>
|
||||||
|
<div class="form-fields">
|
||||||
|
<select class="main-character-field" {{#if selectedLeaderDisabled}}disabled{{/if}}>
|
||||||
|
{{selectOptions selectedLeaderOptions selected=selectedLeader.memberId blank="" }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<button type="button" data-action="startGroupRoll" {{#unless canStartGroupRoll}}disabled{{/unless}}>{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.startGroupRoll"}} <i class="fa-solid fa-arrow-right-long"></i></button>
|
||||||
|
<div class="finish-tools {{#unless canStartGroupRoll}}inactive{{/unless}}">
|
||||||
|
<span>{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.openDialogForAll"}}</span>
|
||||||
|
<input type="checkbox" class="openforall-field" {{#unless canStartGroupRoll}}disabled{{/unless}} {{checked openForAllPlayers}} />
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</section>
|
||||||
73
templates/dialogs/groupRollDialog/leader.hbs
Normal file
73
templates/dialogs/groupRollDialog/leader.hbs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<section class="tab {{#if tabs.groupRoll.active}} active{{/if}}" data-group="{{tabs.groupRoll.group}}" data-tab="{{tabs.groupRoll.id}}">
|
||||||
|
{{#with leader}}
|
||||||
|
<div class="main-character-outer-container {{#unless isEditable}}inactive{{/unless}}">
|
||||||
|
<div class="section-title">{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leader"}}</div>
|
||||||
|
<fieldset>
|
||||||
|
<div class="main-character-container">
|
||||||
|
<div class="character-info">
|
||||||
|
<img src="{{img}}" />
|
||||||
|
<div class="character-data">
|
||||||
|
<span>{{name}}</span>
|
||||||
|
<div class="roll-setup">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-fields">
|
||||||
|
<select name="system.groupRoll.leader.rollChoice" {{#if hasRolled}}disabled{{/if}}>
|
||||||
|
{{selectOptions ../traitOptions selected=rollChoice localize=true}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if readyToRoll}}
|
||||||
|
<div class="roll-container">
|
||||||
|
<span class="roll-title">
|
||||||
|
{{localize "DAGGERHEART.GENERAL.roll"}}
|
||||||
|
<div class="roll-tools">
|
||||||
|
<a class="roll-button" data-action="makeLeaderRoll">
|
||||||
|
<img src="systems/daggerheart/assets/icons/dice/hope/d12.svg" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{{#if hasRolled}}
|
||||||
|
<a class="delete-button" data-action="removeLeaderRoll">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{{#if roll}}
|
||||||
|
<div class="roll-data {{#if roll.withHope}}hope{{else if roll.withFear}}fear{{else}}critical{{/if}}">
|
||||||
|
<div class="duality-label">{{roll.total}} {{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}</div>
|
||||||
|
<div class="roll-dice-container">
|
||||||
|
<a class="roll-dice" data-action="rerollLeaderDice" data-dice-type="hope">
|
||||||
|
<span class="dice-label">{{roll.dHope.total}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" roll.dHope.denomination ".svg"}}" />
|
||||||
|
</a>
|
||||||
|
<span class="roll-operator">+</span>
|
||||||
|
<a class="roll-dice" data-action="rerollLeaderDice" data-dice-type="fear">
|
||||||
|
<span class="dice-label">{{roll.dFear.total}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/fear/" roll.dFear.denomination ".svg"}}" />
|
||||||
|
</a>
|
||||||
|
{{#if roll.advantage.type}}
|
||||||
|
<span class="roll-operator">{{#if (eq roll.advantage.type 1)}}+{{else}}-{{/if}}</span>
|
||||||
|
<span class="roll-dice">
|
||||||
|
<span class="dice-label">{{roll.advantage.value}}</span>
|
||||||
|
<img src="{{concat "systems/daggerheart/assets/icons/dice/" (ifThen (eq roll.advantage.type 1) "adv/" "disadv/") roll.advantage.dice ".svg"}}" />
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
<span class="roll-operator">{{#if (gte roll.modifierTotal 0)}}+{{else}}-{{/if}}</span>
|
||||||
|
<span class="roll-value">{{positive roll.modifierTotal}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<span class="hint">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.makeYourRoll"}}</span>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{{/with}}
|
||||||
|
</section>
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<section class="initialization-container tab {{#if tabs.initialization.active}} active{{/if}}" data-group="{{tabs.initialization.group}}" data-tab="{{tabs.initialization.id}}">
|
<section class="initialization-container tab {{#if tabs.initialization.active}} active{{/if}}" data-group="{{tabs.initialization.group}}" data-tab="{{tabs.initialization.id}}">
|
||||||
{{partId}}
|
|
||||||
<h2>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}</h2>
|
<h2>{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.selectParticipants"}}</h2>
|
||||||
<div class="members-container">
|
<div class="members-container">
|
||||||
{{#each memberSelection as |member|}}
|
{{#each memberSelection as |member|}}
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,10 @@
|
||||||
<i class="fa-solid fa-user-group"></i>
|
<i class="fa-solid fa-user-group"></i>
|
||||||
<span>Tag Team Roll</span>
|
<span>Tag Team Roll</span>
|
||||||
</button>
|
</button>
|
||||||
<button data-action="groupRoll">
|
<button data-action="groupRoll" class="{{#if groupRollActive}}active-action{{/if}}">
|
||||||
<i class="fa-solid fa-users"></i>
|
<i class="fa-solid fa-users"></i>
|
||||||
<span>Group Roll</span>
|
<span>Group Roll</span>
|
||||||
</button>
|
</button>
|
||||||
{{!-- NOT YET IMPLEMENTED --}}
|
|
||||||
{{!-- <button>
|
|
||||||
<i class="fa-solid fa-handshake-angle"></i>
|
|
||||||
<span>Help Action</span>
|
|
||||||
</button> --}}
|
|
||||||
<button data-action="triggerRest" data-action="triggerRest" data-type="shortRest">
|
<button data-action="triggerRest" data-action="triggerRest" data-type="shortRest">
|
||||||
<i class="fa-solid fa-utensils"></i>
|
<i class="fa-solid fa-utensils"></i>
|
||||||
<span>{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}</span>
|
<span>{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}</span>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue