Compare commits

...

8 commits

Author SHA1 Message Date
WBHarry
dddc8413cb . 2026-04-11 02:29:53 +02:00
WBHarry
7e54ed1218 Localization fixes 2026-04-11 02:28:49 +02:00
WBHarry
4fedb42d44 Renamed 'Main Charater' to 'Leader' 2026-04-11 02:26:01 +02:00
WBHarry
94f9a7a0d2 . 2026-04-11 02:18:18 +02:00
WBHarry
154c1c939b Improvements 2026-04-11 02:02:32 +02:00
WBHarry
303f2fdae7 Merge branch 'main' into feature/group-roll-rework 2026-04-11 00:42:30 +02:00
WBHarry
9bea8d6a97 Merged with main 2026-04-11 00:05:18 +02:00
Carlos Fernandez
7ca420ae0e
[Feature] Redesign and merge party members and resources tabs (#1784) 2026-04-10 15:33:44 -04:00
20 changed files with 769 additions and 584 deletions

View file

@ -319,6 +319,21 @@
} }
}, },
"newAdversary": "New Adversary" "newAdversary": "New Adversary"
},
"Party": {
"Subtitle": {
"character": "{community} {ancestry} | {subclass} {class}",
"companion": "Companion of {partner}"
},
"RemoveConfirmation": {
"title": "Remove member {name}",
"text": "Are you sure you want to remove {name} from the party?"
},
"Thresholds": {
"minor": "MIN",
"major": "MAJ",
"severe": "SEV"
}
} }
}, },
"APPLICATIONS": { "APPLICATIONS": {
@ -535,18 +550,6 @@
}, },
"takeDowntime": "Take Downtime" "takeDowntime": "Take Downtime"
}, },
"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"
},
"HUD": { "HUD": {
"tokenHUD": { "tokenHUD": {
"genericEffects": "Foundry Effects", "genericEffects": "Foundry Effects",
@ -732,7 +735,8 @@
}, },
"GroupRollSelect": { "GroupRollSelect": {
"title": "Group Roll", "title": "Group Roll",
"mainCharacter": "Main Character", "leader": "Leader",
"leaderRoll": "Leader Roll",
"openDialogForAll": "Open Dialog For All", "openDialogForAll": "Open Dialog For All",
"startGroupRoll": "Start Group Roll", "startGroupRoll": "Start Group Roll",
"cancelGroupRoll": "Cancel", "cancelGroupRoll": "Cancel",
@ -2397,7 +2401,6 @@
}, },
"maxWithThing": "Max {thing}", "maxWithThing": "Max {thing}",
"missingDragDropThing": "Drop {thing} here", "missingDragDropThing": "Drop {thing} here",
"modifier": "Modifier",
"multiclass": "Multiclass", "multiclass": "Multiclass",
"newCategory": "New Category", "newCategory": "New Category",
"newThing": "New {thing}", "newThing": "New {thing}",

View file

@ -1,3 +1,4 @@
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs'; import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import Party from '../sheets/actors/party.mjs'; import Party from '../sheets/actors/party.mjs';
@ -18,7 +19,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER) owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
})); }));
this.mainCharacter = null; this.leader = null;
this.openForAllPlayers = true; this.openForAllPlayers = true;
this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length
@ -29,7 +30,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
} }
get title() { get title() {
return game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRoll.title'); return game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title');
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
@ -43,9 +44,9 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
makeRoll: this.#makeRoll, makeRoll: this.#makeRoll,
removeRoll: this.#removeRoll, removeRoll: this.#removeRoll,
rerollDice: this.#rerollDice, rerollDice: this.#rerollDice,
makeMainCharacterRoll: this.#makeMainCharacterRoll, makeLeaderRoll: this.#makeLeaderRoll,
removeMainCharacterRoll: this.#removeMainCharacterRoll, removeLeaderRoll: this.#removeLeaderRoll,
rerollMainCharacterDice: this.#rerollMainCharacterDice, rerollLeaderDice: this.#rerollLeaderDice,
markSuccessfull: this.#markSuccessfull, markSuccessfull: this.#markSuccessfull,
cancelRoll: this.#onCancelRoll, cancelRoll: this.#onCancelRoll,
finishRoll: this.#finishRoll finishRoll: this.#finishRoll
@ -58,9 +59,9 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
id: 'initialization', id: 'initialization',
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs' template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs'
}, },
mainCharacter: { leader: {
id: 'mainCharacter', id: 'leader',
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMainCharacter.hbs' template: 'systems/daggerheart/templates/dialogs/groupRollDialog/leader.hbs'
}, },
groupRoll: { groupRoll: {
id: 'groupRoll', id: 'groupRoll',
@ -84,11 +85,11 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
htmlElement htmlElement
.querySelector('.main-character-field') .querySelector('.main-character-field')
?.addEventListener('input', this.updateMainCharacterField.bind(this)); ?.addEventListener('input', this.updateLeaderField.bind(this));
} }
_configureRenderParts(options) { _configureRenderParts(options) {
const { initialization, mainCharacter, groupRoll, footer } = super._configureRenderParts(options); const { initialization, leader, groupRoll, footer } = super._configureRenderParts(options);
const augmentedParts = { initialization }; const augmentedParts = { initialization };
for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) { for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) {
augmentedParts[memberKey] = { augmentedParts[memberKey] = {
@ -97,7 +98,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
}; };
} }
augmentedParts.mainCharacter = mainCharacter; augmentedParts.leader = leader;
augmentedParts.groupRoll = groupRoll; augmentedParts.groupRoll = groupRoll;
augmentedParts.footer = footer; augmentedParts.footer = footer;
@ -109,15 +110,19 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
await super._onRender(context, options); await super._onRender(context, options);
if (this.element.querySelector('.team-container')) return; if (this.element.querySelector('.team-container')) return;
const initializationPart = this.element.querySelector('.initialization-container');
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>'); if (this.tabGroups.application !== this.constructor.PARTS.initialization.id) {
initializationPart.insertAdjacentHTML( const initializationPart = this.element.querySelector('.initialization-container');
'afterend', initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
`<div class="section-title">${game.i18n.localize('Aiding Characters')}</div>` initializationPart.insertAdjacentHTML(
); 'afterend',
const teamContainer = this.element.querySelector('.team-container'); `<div class="section-title">${game.i18n.localize('Aiding Characters')}</div>`
for (const memberContainer of this.element.querySelectorAll('.team-member-container')) );
teamContainer.appendChild(memberContainer);
const teamContainer = this.element.querySelector('.team-container');
for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
teamContainer.appendChild(memberContainer);
}
} }
async _prepareContext(_options) { async _prepareContext(_options) {
@ -148,19 +153,25 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
const selectedMembers = partContext.memberSelection.filter(x => x.selected); const selectedMembers = partContext.memberSelection.filter(x => x.selected);
partContext.selectedMainCharacter = this.mainCharacter; partContext.selectedLeader = this.leader;
partContext.selectedMainCharacterOptions = selectedMembers partContext.selectedLeaderOptions = selectedMembers
.filter(actor => actor.owned) .filter(actor => actor.owned)
.map(x => ({ value: x.id, label: x.name })); .map(x => ({ value: x.id, label: x.name }));
partContext.selectedMainCharacterDisabled = !selectedMembers.length; partContext.selectedLeaderDisabled = !selectedMembers.length;
partContext.canStartGroupRoll = selectedMembers.length > 1; partContext.canStartGroupRoll = selectedMembers.length > 1 && this.leader?.memberId;
partContext.openForAllPlayers = this.openForAllPlayers; partContext.openForAllPlayers = this.openForAllPlayers;
break; break;
case 'mainCharacter': case 'leader':
partContext.mainCharacter = this.getRollCharacterData(this.party.system.groupRoll.mainCharacter); partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
break; break;
case 'groupRoll': 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( const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce(
(acc, curr) => { (acc, curr) => {
const modifier = curr.successfull === true ? 1 : curr.successfull === false ? -1 : null; const modifier = curr.successfull === true ? 1 : curr.successfull === false ? -1 : null;
@ -173,23 +184,22 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
}, },
{ modifierTotal: 0, modifiers: [] } { modifierTotal: 0, modifiers: [] }
); );
const mainCharacterTotal = this.party.system.groupRoll.mainCharacter?.rollData const leaderTotal = leader?.rollData ? leader.roll.total : null;
? this.party.system.groupRoll.mainCharacter.roll.total
: null;
partContext.groupRoll = { partContext.groupRoll = {
totalLabel: this.party.system.groupRoll.mainCharacter?.rollData totalLabel: leader?.rollData
? game.i18n.format('DAGGERHEART.GENERAL.withThing', { ? game.i18n.format('DAGGERHEART.GENERAL.withThing', {
thing: this.party.system.groupRoll.mainCharacter.roll.totalLabel thing: leader.roll.totalLabel
}) })
: null, : null,
total: mainCharacterTotal + modifierTotal, totalDualityClass: leader?.roll?.isCritical ? 'critical' : leader?.roll?.withHope ? 'hope' : 'fear',
mainCharacterTotal, total: leaderTotal + modifierTotal,
leaderTotal: leaderTotal,
modifiers modifiers
}; };
break; break;
case 'footer': case 'footer':
partContext.canFinishRoll = partContext.canFinishRoll =
Boolean(this.party.system.groupRoll.mainCharacter?.rollData) && Boolean(this.party.system.groupRoll.leader?.rollData) &&
Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successfull !== null); Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successfull !== null);
break; break;
} }
@ -246,15 +256,15 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
} }
getUpdatingParts(target) { getUpdatingParts(target) {
const { initialization, mainCharacter, groupRoll, footer } = this.constructor.PARTS; const { initialization, leader, groupRoll, footer } = this.constructor.PARTS;
const isInitialization = this.tabGroups.application === initialization.id; const isInitialization = this.tabGroups.application === initialization.id;
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey; const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
const updatingMainCharacter = target.closest('.main-character-outer-container'); const updatingLeader = target.closest('.main-character-outer-container');
return [ return [
...(isInitialization ? [initialization.id] : []), ...(isInitialization ? [initialization.id] : []),
...(updatingMember ? [updatingMember] : []), ...(updatingMember ? [updatingMember] : []),
...(updatingMainCharacter ? [mainCharacter.id] : []), ...(updatingLeader ? [leader.id] : []),
...(!isInitialization ? [groupRoll.id, footer.id] : []) ...(!isInitialization ? [groupRoll.id, footer.id] : [])
]; ];
} }
@ -297,16 +307,16 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
this.render(); this.render();
} }
updateMainCharacterField(event) { updateLeaderField(event) {
if (!this.mainCharacter) this.mainCharacter = {}; if (!this.leader) this.leader = {};
this.mainCharacter.memberId = event.target.value; this.leader.memberId = event.target.value;
this.render(); this.render();
} }
static async #startGroupRoll() { static async #startGroupRoll() {
const mainCharacter = this.partyMembers.find(x => x.id === this.mainCharacter.memberId); const leader = this.partyMembers.find(x => x.id === this.leader.memberId);
const aidingCharacters = this.partyMembers.reduce((acc, curr) => { const aidingCharacters = this.partyMembers.reduce((acc, curr) => {
if (curr.selected && curr.id !== this.mainCharacter.memberId) if (curr.selected && curr.id !== this.leader.memberId)
acc[curr.id] = { id: curr.id, name: curr.name, img: curr.img }; acc[curr.id] = { id: curr.id, name: curr.name, img: curr.img };
return acc; return acc;
@ -316,7 +326,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
'system.groupRoll': _replace( 'system.groupRoll': _replace(
new game.system.api.data.GroupRollData({ new game.system.api.data.GroupRollData({
...this.party.system.groupRoll.toObject(), ...this.party.system.groupRoll.toObject(),
mainCharacter: { id: mainCharacter.id, name: mainCharacter.name, img: mainCharacter.img }, leader: { id: leader.id, name: leader.name, img: leader.img },
aidingCharacters aidingCharacters
}) })
) )
@ -333,13 +343,10 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
} }
//#endregion //#endregion
static async #makeRoll(_event, button) { async makeRoll(button, characterData, path) {
const { member } = button.dataset; const actor = game.actors.find(x => x.id === characterData.id);
const actor = game.actors.find(x => x.id === member);
if (!actor) return; if (!actor) return;
const characterData = this.party.system.groupRoll.aidingCharacters[member];
const result = await actor.rollTrait(characterData.rollChoice, { const result = await actor.rollTrait(characterData.rollChoice, {
skips: { skips: {
createMessage: true, createMessage: true,
@ -354,36 +361,43 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
delete rollData.options.messageRoll; delete rollData.options.messageRoll;
this.updatePartyData( this.updatePartyData(
{ {
[`system.groupRoll.aidingCharacters.${member}.rollData`]: rollData [path]: rollData
}, },
this.getUpdatingParts(button) this.getUpdatingParts(button)
); );
} }
static async #removeRoll(_, 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( this.updatePartyData(
{ {
[`system.groupRoll.aidingCharacters.${button.dataset.member}`]: { [path]: {
rollData: null, rollData: null,
rollChoice: null, rollChoice: null,
selected: false selected: false,
successfull: null
} }
}, },
this.getUpdatingParts(button) this.getUpdatingParts(button)
); );
} }
static async #rerollDice(_, button) { static async #removeRoll(_event, button) {
const { member } = button.dataset; this.removeRoll(button, `system.groupRoll.aidingCharacters.${button.dataset.member}`);
this.rerollDice(
button,
this.party.system.groupRoll.aidingCharacters[member],
`system.groupRoll.aidingCharacters.${member}.rollData`
);
} }
static async #rerollMainCharacterDice(_, button) { static async #removeLeaderRoll(_event, button) {
this.rerollDice(button, this.party.system.groupRoll.mainCharacter, `system.groupRoll.mainCharacter.rollData`); this.removeRoll(button, 'system.groupRoll.leader');
} }
async rerollDice(button, data, path) { async rerollDice(button, data, path) {
@ -407,42 +421,17 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
); );
} }
static async #makeMainCharacterRoll(_event, button) { static async #rerollDice(_, button) {
const actor = game.actors.find(x => x.id === this.party.system.groupRoll.mainCharacter.id); const { member } = button.dataset;
if (!actor) return; this.rerollDice(
button,
const characterData = this.party.system.groupRoll.mainCharacter; this.party.system.groupRoll.aidingCharacters[member],
const result = await actor.rollTrait(characterData.rollChoice, { `system.groupRoll.aidingCharacters.${member}.rollData`
skips: {
createMessage: true,
resources: true,
triggers: true
}
});
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(
{
[`system.groupRoll.mainCharacter.rollData`]: rollData
},
this.getUpdatingParts(button)
); );
} }
static async #removeMainCharacterRoll(_event, button) { static async #rerollLeaderDice(_, button) {
this.updatePartyData( this.rerollDice(button, this.party.system.groupRoll.leader, `system.groupRoll.leader.rollData`);
{
[`system.groupRoll.mainCharacter`]: {
rollData: null,
rollChoice: null,
selected: false
}
},
this.getUpdatingParts(button)
);
} }
static #markSuccessfull(_event, button) { static #markSuccessfull(_event, button) {
@ -476,7 +465,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
await this.updatePartyData( await this.updatePartyData(
{ {
'system.groupRoll': { 'system.groupRoll': {
mainCharacter: null, leader: null,
aidingCharacters: _replace({}) aidingCharacters: _replace({})
} }
}, },
@ -492,7 +481,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
} }
static async #finishRoll() { static async #finishRoll() {
const totalRoll = this.party.system.groupRoll.mainCharacter.roll; const totalRoll = this.party.system.groupRoll.leader.roll;
for (const character of Object.values(this.party.system.groupRoll.aidingCharacters)) { 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.OperatorTerm({ operator: character.successfull ? '+' : '-' }));
totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 })); totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 }));
@ -501,13 +490,13 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
await totalRoll._evaluate(); await totalRoll._evaluate();
const systemData = totalRoll.options; const systemData = totalRoll.options;
const actor = game.actors.get(this.party.system.groupRoll.mainCharacter.id); const actor = game.actors.get(this.party.system.groupRoll.leader.id);
const cls = getDocumentClass('ChatMessage'), const cls = getDocumentClass('ChatMessage'),
msgData = { msgData = {
type: 'dualityRoll', type: 'dualityRoll',
user: game.user.id, user: game.user.id,
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'), title: game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.title'),
speaker: cls.getSpeaker({ actor }), speaker: cls.getSpeaker({ actor }),
system: systemData, system: systemData,
rolls: [JSON.stringify(totalRoll)], rolls: [JSON.stringify(totalRoll)],
@ -517,6 +506,20 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
await cls.create(msgData); 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 */ /* Fin */
this.cancelRoll({ confirm: false }); this.cancelRoll({ confirm: false });
} }

View file

@ -1,5 +1,5 @@
import DHBaseActorSheet from '../api/base-actor.mjs'; import DHBaseActorSheet from '../api/base-actor.mjs';
import { getDocFromElement } from '../../../helpers/utils.mjs'; import { getDocFromElement, sortBy } from '../../../helpers/utils.mjs';
import { ItemBrowser } from '../../ui/itemBrowser.mjs'; 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';
@ -17,13 +17,14 @@ export default class Party extends DHBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['party'], classes: ['party'],
position: { position: {
width: 550, width: 600,
height: 900 height: 900
}, },
window: { window: {
resizable: true resizable: true
}, },
actions: { actions: {
openDocument: Party.#openDocument,
deletePartyMember: Party.#deletePartyMember, deletePartyMember: Party.#deletePartyMember,
deleteItem: Party.#deleteItem, deleteItem: Party.#deleteItem,
toggleHope: Party.#toggleHope, toggleHope: Party.#toggleHope,
@ -44,10 +45,6 @@ export default class Party extends DHBaseActorSheet {
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' }, header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' }, tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' }, partyMembers: { template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs' },
resources: {
template: 'systems/daggerheart/templates/sheets/actors/party/resources.hbs',
scrollable: ['']
},
/* NOT YET IMPLEMENTED */ /* NOT YET IMPLEMENTED */
// projects: { // projects: {
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs', // template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
@ -65,7 +62,6 @@ export default class Party extends DHBaseActorSheet {
primary: { primary: {
tabs: [ tabs: [
{ id: 'partyMembers' }, { id: 'partyMembers' },
{ id: 'resources' },
/* NOT YET IMPLEMENTED */ /* NOT YET IMPLEMENTED */
// { id: 'projects' }, // { id: 'projects' },
{ id: 'inventory' }, { id: 'inventory' },
@ -95,6 +91,8 @@ export default class Party extends DHBaseActorSheet {
case 'header': case 'header':
await this._prepareHeaderContext(context, options); await this._prepareHeaderContext(context, options);
break; break;
case 'partyMembers':
await this._prepareMembersContext(context, options);
case 'notes': case 'notes':
await this._prepareNotesContext(context, options); await this._prepareNotesContext(context, options);
break; break;
@ -118,6 +116,60 @@ 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) {
context.partyMembers = [];
const traits = ['agility', 'strength', 'finesse', 'instinct', 'presence', 'knowledge'];
for (const actor of this.document.system.partyMembers) {
const weapons = [];
if (actor.type === 'character') {
if (actor.system.usedUnarmed) {
weapons.push(actor.system.usedUnarmed);
}
const equipped = actor.items.filter(i => i.system.equipped && i.type === 'weapon');
weapons.push(...sortBy(equipped, i => (i.system.secondary ? 1 : 0)));
}
context.partyMembers.push({
uuid: actor.uuid,
img: actor.img,
name: actor.name,
subtitle: (() => {
if (!['character', 'companion'].includes(actor.type)) {
return game.i18n.format(`TYPES.Actor.${actor.type}`);
}
const { value: classItem, subclass } = actor.system.class ?? {};
const partner = actor.system.partner;
const ancestry = actor.system.ancestry;
const community = actor.system.community;
if (partner || (classItem && subclass && ancestry && community)) {
return game.i18n.format(`DAGGERHEART.ACTORS.Party.Subtitle.${actor.type}`, {
class: classItem?.name,
subclass: subclass?.name,
partner: partner?.name,
ancestry: ancestry?.name,
community: community?.name
});
}
})(),
type: actor.type,
resources: actor.system.resources,
armorScore: actor.system.armorScore,
damageThresholds: actor.system.damageThresholds,
evasion: actor.system.evasion,
difficulty: actor.system.difficulty,
traits: actor.system.traits
? traits.map(t => ({
label: game.i18n.localize(`DAGGERHEART.CONFIG.Traits.${t}.short`),
value: actor.system.traits[t].value
}))
: null,
weapons
});
}
} }
/** /**
@ -148,6 +200,12 @@ export default class Party extends DHBaseActorSheet {
} }
} }
static async #openDocument(_, target) {
const uuid = target.dataset.uuid;
const document = await foundry.utils.fromUuid(uuid);
document?.sheet?.render(true);
}
/** /**
* Toggles a hope resource value. * Toggles a hope resource value.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
@ -189,7 +247,7 @@ export default class Party extends DHBaseActorSheet {
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleArmorSlot(_, target) { static async #toggleArmorSlot(_, target) {
const actor = game.actors.get(target.dataset.actorId); const actor = await foundry.utils.fromUuid(target.dataset.actorId);
const { value, max } = actor.system.armorScore; const { value, max } = actor.system.armorScore;
const inputValue = Number.parseInt(target.dataset.value); const inputValue = Number.parseInt(target.dataset.value);
const newValue = value >= inputValue ? inputValue - 1 : inputValue; const newValue = value >= inputValue ? inputValue - 1 : inputValue;
@ -422,25 +480,23 @@ export default class Party extends DHBaseActorSheet {
} }
static async #deletePartyMember(event, target) { static async #deletePartyMember(event, target) {
const doc = await getDocFromElement(target.closest('.inventory-item')); const doc = await foundry.utils.fromUuid(target.closest('[data-uuid]')?.dataset.uuid);
if (!event.shiftKey) { if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', { title: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.title', {
type: game.i18n.localize('TYPES.Actor.adversary'),
name: doc.name name: doc.name
}) })
}, },
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name }) content: game.i18n.format('DAGGERHEART.ACTORS.Party.RemoveConfirmation.text', { name: doc.name })
}); });
if (!confirmed) return; if (!confirmed) return;
} }
const currentMembers = this.document.system.partyMembers.map(x => x.uuid); const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid); const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
await this.document.update({ 'system.partyMembers': newMemberdList }); await this.document.update({ 'system.partyMembers': newMembersList });
} }
static async #deleteItem(event, target) { static async #deleteItem(event, target) {

View file

@ -3,14 +3,14 @@ export default class GroupRollData extends foundry.abstract.DataModel {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
mainCharacter: new fields.EmbeddedDataField(CharacterData, { nullable: true, initial: null }), leader: new fields.EmbeddedDataField(CharacterData, { nullable: true, initial: null }),
aidingCharacters: new fields.TypedObjectField(new fields.EmbeddedDataField(CharacterData)) aidingCharacters: new fields.TypedObjectField(new fields.EmbeddedDataField(CharacterData))
}; };
} }
get participants() { get participants() {
return { return {
...(this.mainCharacter ? { [this.mainCharacter.id]: this.mainCharacter } : {}), ...(this.leader ? { [this.leader.id]: this.leader } : {}),
...this.aidingCharacters ...this.aidingCharacters
}; };
} }

View file

@ -1,3 +1,11 @@
.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 { .daggerheart.dialog.dh-style.views.group-roll-dialog {
.initialization-container { .initialization-container {
h2 { h2 {
@ -20,6 +28,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);
} }
} }
} }

View file

@ -12,6 +12,11 @@
gap: 8px; gap: 8px;
flex: 1; flex: 1;
&.inactive {
opacity: 0.3;
pointer-events: none;
}
.data-container { .data-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -59,9 +64,27 @@
align-items: center; align-items: center;
gap: 8px; gap: 8px;
&.hope,
&.fear,
&.critical {
color: var(--text-color);
}
&.hope {
--text-color: @golden;
}
&.fear {
--text-color: @chat-blue;
}
&.critical {
--text-color: @chat-purple;
}
&::before, &::before,
&::after { &::after {
color: light-dark(@dark-blue, @golden); color: var(--text-color);
content: ''; content: '';
flex: 1; flex: 1;
height: 2px; height: 2px;

View file

@ -37,7 +37,7 @@
@import './tag-team-dialog/sheet.less'; @import './tag-team-dialog/sheet.less';
@import './group-roll-dialog/initialization.less'; @import './group-roll-dialog/initialization.less';
@import './group-roll-dialog/mainCharacter.less'; @import './group-roll-dialog/leader.less';
@import './group-roll-dialog/sheet.less'; @import './group-roll-dialog/sheet.less';
@import './image-select/sheet.less'; @import './image-select/sheet.less';

View file

@ -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);
} }
} }
} }

View file

@ -1,28 +1,293 @@
@import '../../../utils/colors.less'; @import '../../../utils/colors.less';
@import '../../../utils/fonts.less'; @import '../../../utils/fonts.less';
@import '../../../utils/mixin.less';
.application.sheet.daggerheart.actor.dh-style.party { body.game:is(.performance-low, .noblur) {
.tab.partyMembers { .application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources {
max-height: 400px; background: light-dark(@dark-blue, @dark-golden);
overflow: auto;
.actors-list { .actor-name {
display: flex; background: light-dark(@dark-blue, @dark-golden);
flex-direction: column;
gap: 10px;
align-items: center;
width: 100%;
}
.actors-dragger {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 100%;
height: 40px;
border: 1px dashed light-dark(@dark-blue-50, @beige-50);
border-radius: 3px;
color: light-dark(@dark-blue-50, @beige-50);
} }
} }
} }
.application.sheet.daggerheart.actor.dh-style.party .tab.partyMembers {
overflow: auto;
.actors-list {
display: flex;
flex-direction: column;
gap: 8px;
align-items: stretch;
width: 100%;
.actor-resources {
display: grid;
grid-template:
"img header" min-content
"img body" 1fr
/ 7.5rem 1fr;
gap: 6px;
column-gap: 12px;
padding: 6px;
background-color: light-dark(@dark-blue-10, @golden-10);
.actor-img-frame {
grid-area: img;
width: 7.5rem;
height: 7.5rem;
position: relative;
.actor-img {
object-fit: cover;
object-position: top center;
border-radius: 6px;
width: 100%;
height: 100%;
}
.equipped-weapons {
position: absolute;
top: -2px;
left: -3px;
display: flex;
flex-direction: column;
gap: 1px;
img {
border-radius: 50%;
width: 24px;
height: 24px;
border: 1px solid light-dark(@dark-blue, @golden);
object-fit: cover;
}
}
.evasion {
position: absolute;
top: 1px;
right: 1px;
width: 1.75rem;
height: 1.75rem;
background: url('../assets/svg/trait-shield.svg') no-repeat;
background-size: 100%;
font-size: var(--font-size-14);
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
}
.threshold-section {
position: absolute;
left: 0;
right: 0;
bottom: -2px;
margin: auto;
display: flex;
gap: 4px;
background-color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 4px 6px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 3px;
align-items: baseline;
width: fit-content;
h4 {
font-weight: bold;
text-transform: uppercase;
white-space: nowrap;
&.threshold-label {
font-size: var(--font-size-10);
color: light-dark(@dark-blue, @golden);
}
&.threshold-value {
font-size: var(--font-size-11);
color: light-dark(@dark, @beige);
}
}
}
}
header {
grid-area: header;
display: grid;
grid-template:
"name hope" min-content
"subtitle subtitle" min-content
/ 1fr min-content;
.actor-name {
width: 100%;
z-index: 1;
font-size: var(--font-size-20);
color: light-dark(@beige, @golden);
font-weight: bold;
}
.delete-icon {
font-size: 0.75em;
}
.subtitle {
grid-area: subtitle;
font-size: var(--font-size-14);
}
.hope-section {
display: flex;
background-color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 3px 6px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 3px;
align-items: center;
width: fit-content;
margin-left: auto;
h4 {
font-size: var(--font-size-12);
font-weight: bold;
text-transform: uppercase;
color: light-dark(@dark-blue, @golden);
margin-right: 3px;
}
.hope-value {
display: flex;
cursor: pointer;
font-size: var(--font-size-12);
margin-left: 1px;
}
}
}
.body {
grid-area: body;
display: flex;
align-items: start;
justify-content: space-between;
}
.resources {
display: flex;
flex-direction: column;
gap: 4px;
.slot-section {
display: flex;
flex-direction: row;
align-items: stretch;
.slot-label {
display: flex;
align-items: center;
color: light-dark(@beige, @dark-blue);
background: light-dark(@dark-blue, @golden);
padding: 0 4px;
width: fit-content;
font-weight: bold;
border-radius: 6px 0px 0px 6px;
font-size: var(--font-size-12);
white-space: nowrap;
.label {
padding-right: 2px;
}
.value {
font-variant-numeric: tabular-nums;
.current {
display: inline-block;
text-align: end;
width: 2ch;
}
.max {
display: inline-block;
text-align: start;
width: 2ch;
}
}
}
.slot-bar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
background-color: light-dark(@dark-blue-10, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 2px 5px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 0 6px 6px 0;
width: fit-content;
min-height: 22px;
.armor-slot {
cursor: pointer;
transition: all 0.3s ease;
font-size: var(--font-size-12);
.fa-shield-halved {
color: light-dark(@dark-blue-40, @golden-40);
}
}
.slot {
width: 16px;
height: 10px;
border: 1px solid light-dark(@dark-blue, @golden);
background: light-dark(@dark-blue-10, @golden-10);
border-radius: 3px;
transition: all 0.3s ease;
cursor: pointer;
&.filled {
background: light-dark(@dark-blue, @golden);
}
}
}
}
}
.traits {
background-color: light-dark(@dark-blue-10, @dark-blue);
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
display: grid;
grid-template-columns: 1fr 1fr;
font-size: var(--font-size-12);
padding: 3px 4px;
gap: 3px 7px;
.trait {
display: flex;
justify-content: space-between;
gap: 3px;
.label {
color: light-dark(@dark-blue, @golden);
}
.value {
font-weight: 600;
}
}
}
}
}
.actors-dragger {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 100%;
height: 40px;
border: 1px dashed light-dark(@dark-blue-50, @beige-50);
border-radius: 3px;
color: light-dark(@dark-blue-50, @beige-50);
}
}

View file

@ -1,216 +0,0 @@
@import '../../../utils/colors.less';
@import '../../../utils/fonts.less';
@import '../../../utils/mixin.less';
body.game:is(.performance-low, .noblur) {
.application.sheet.daggerheart.actor.dh-style.party .tab.resources .actors-list .actor-resources {
background: light-dark(@dark-blue, @dark-golden);
.actor-name {
background: light-dark(@dark-blue, @dark-golden);
}
}
}
.application.sheet.daggerheart.actor.dh-style.party {
.tab.resources {
overflow: auto;
.actors-list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
align-items: center;
width: 100%;
justify-content: center;
.actor-resources {
display: flex;
flex-direction: column;
position: relative;
background: light-dark(@dark-blue-40, @dark-golden-40);
border-radius: 6px;
border: 1px solid light-dark(@dark-blue, @golden);
width: 230px;
height: -webkit-fill-available;
.actor-name {
position: absolute;
top: 0px;
background: light-dark(@dark-blue-90, @dark-golden-80);
backdrop-filter: blur(6.5px);
border-radius: 6px 6px 0px 0px;
text-align: center;
width: 100%;
z-index: 1;
font-size: var(--font-size-20);
color: light-dark(@beige, @golden);
font-weight: bold;
padding: 5px 0;
}
.actor-img {
height: 150px;
object-fit: cover;
object-position: top center;
border-radius: 6px 6px 0px 0px;
mask-image: linear-gradient(180deg, black 88%, transparent 100%);
}
.resources {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
margin: 10px;
.slot-section {
display: flex;
flex-direction: column;
align-items: center;
.slot-bar {
display: flex;
flex-wrap: wrap;
gap: 5px;
width: 239px;
background-color: light-dark(@dark-blue-10, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 5px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
width: fit-content;
.armor-slot {
cursor: pointer;
transition: all 0.3s ease;
font-size: var(--font-size-12);
.fa-shield-halved {
color: light-dark(@dark-blue-40, @golden-40);
}
}
.slot {
width: 20px;
height: 10px;
border: 1px solid light-dark(@dark-blue, @golden);
background: light-dark(@dark-blue-10, @golden-10);
border-radius: 3px;
transition: all 0.3s ease;
cursor: pointer;
&.filled {
background: light-dark(@dark-blue, @golden);
}
}
}
.slot-label {
display: flex;
align-items: center;
color: light-dark(@beige, @dark-blue);
background: light-dark(@dark-blue, @golden);
padding: 0 5px;
width: fit-content;
font-weight: bold;
border-radius: 0px 0px 5px 5px;
font-size: var(--font-size-12);
.label {
padding-right: 5px;
}
.value {
padding-left: 6px;
border-left: 1px solid light-dark(@beige, @dark-golden);
}
}
}
.hope-section {
position: relative;
display: flex;
gap: 10px;
background-color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 5px 10px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 3px;
align-items: center;
width: fit-content;
h4 {
font-size: var(--font-size-12);
font-weight: bold;
text-transform: uppercase;
color: light-dark(@dark-blue, @golden);
}
.hope-value {
display: flex;
cursor: pointer;
font-size: var(--font-size-12);
}
}
.stat-section {
position: relative;
display: flex;
gap: 10px;
background-color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 5px 10px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 3px;
align-items: center;
width: fit-content;
h4 {
font-size: var(--font-size-12);
font-weight: bold;
text-transform: uppercase;
color: light-dark(@dark-blue, @golden);
}
}
.threshold-section {
display: flex;
align-self: center;
gap: 10px;
background-color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 5px 10px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 3px;
align-items: center;
width: fit-content;
h4 {
font-size: var(--font-size-12);
font-weight: bold;
text-transform: uppercase;
color: light-dark(@dark-blue, @golden);
&.threshold-value {
color: light-dark(@dark, @beige);
}
}
}
}
}
}
.actors-dragger {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 100%;
height: 40px;
border: 1px dashed light-dark(@dark-blue-50, @beige-50);
border-radius: 3px;
color: light-dark(@dark-blue-50, @beige-50);
}
}
}

View file

@ -31,7 +31,6 @@
@import './actors/party/party-members.less'; @import './actors/party/party-members.less';
@import './actors/party/sheet.less'; @import './actors/party/sheet.less';
@import './actors/party/inventory.less'; @import './actors/party/inventory.less';
@import './actors/party/resources.less';
@import './items/beastform.less'; @import './items/beastform.less';
@import './items/class.less'; @import './items/class.less';

View file

@ -1,15 +1,19 @@
<section class="tab {{#if tabs.groupRoll.active}} active{{/if}}" data-group="{{tabs.groupRoll.group}}" data-tab="{{tabs.groupRoll.id}}"> <section class="tab {{#if tabs.groupRoll.active}} active{{/if}}" data-group="{{tabs.groupRoll.group}}" data-tab="{{tabs.groupRoll.id}}">
<fieldset> <fieldset>
<legend>{{localize "Result"}}</legend> <legend>{{localize "DAGGERHEART.GENERAL.result.single"}}</legend>
<div class="group-roll-results"> <div class="group-roll-results">
<span class="roll-title">{{groupRoll.total}} {{groupRoll.totalLabel}}</span> {{#if hasRolled}}<span class="roll-title {{groupRoll.totalDualityClass}}">{{groupRoll.total}} {{groupRoll.totalLabel}}</span>{{/if}}
<div class="group-roll-container"> <div class="group-roll-container">
<span>{{#if groupRoll.mainCharacterTotal includeZero=true}}{{groupRoll.mainCharacterTotal}}{{else}}{{localize "<Main Character Roll>"}}{{/if}}</span> <span>{{#if groupRoll.leaderTotal includeZero=true}}{{groupRoll.leaderTotal}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leaderRoll"}}{{/if}}</span>
{{#each groupRoll.modifiers as |modifier|}} {{#each groupRoll.modifiers as |modifier|}}
<span>{{#if (gte modifier 0)}}+{{else}}-{{/if}}</span> <span>{{#if (gte modifier 0)}}+{{else}}-{{/if}}</span>
<span>{{positive modifier}}</span> <span>{{positive modifier}}</span>
{{/each}} {{/each}}
{{#unless groupRoll.modifiers.length}}
<span>+</span>
<span>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</span>
{{/unless}}
</div> </div>
</div> </div>
</fieldset> </fieldset>

View file

@ -1,71 +0,0 @@
{{#with mainCharacter}}
<div class="main-character-outer-container">
<div class="section-title">{{localize "Main Character"}}</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.mainCharacter.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="makeMainCharacterRoll">
<img src="systems/daggerheart/assets/icons/dice/hope/d12.svg" />
</a>
{{#if hasRolled}}
<a class="delete-button" data-action="removeMainCharacterRoll">
<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="rerollMainCharacterDice" 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="rerollMainCharacterDice" 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}}

View file

@ -76,7 +76,7 @@
</div> </div>
{{/if}} {{/if}}
<div class="roll-success-modifier {{#if successfull}}success{{else if (not (isNullish successfull))}}failure{{/if}}"> <div class="roll-success-modifier {{#if successfull}}success{{else if (not (isNullish successfull))}}failure{{/if}}">
{{localize "DAGGERHEART.GENERAL.modifier"}}{{#if successfull}} + 1{{else if (isNullish successfull)}} + ?{{else}} - 1{{/if}} {{localize "DAGGERHEART.GENERAL.Modifier.single"}}{{#if successfull}} + 1{{else if (isNullish successfull)}} + ?{{else}} - 1{{/if}}
</div> </div>
</div> </div>
{{/if}} {{/if}}

View file

@ -11,12 +11,12 @@
{{/each}} {{/each}}
</div> </div>
<div class="main-roll {{#if selectedMainCharacterDisabled}}inactive{{/if}}"> <div class="main-roll {{#if selectedLeaderDisabled}}inactive{{/if}}">
<div class="form-group"> <div class="form-group">
<label>{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.mainCharacter"}}</label> <label>{{localize "DAGGERHEART.APPLICATIONS.GroupRollSelect.leader"}}</label>
<div class="form-fields"> <div class="form-fields">
<select class="main-character-field" {{#if selectedMainCharacterDisabled}}disabled{{/if}}> <select class="main-character-field" {{#if selectedLeaderDisabled}}disabled{{/if}}>
{{selectOptions selectedMainCharacterOptions selected=selectedMainCharacter.memberId blank="" }} {{selectOptions selectedLeaderOptions selected=selectedLeader.memberId blank="" }}
</select> </select>
</div> </div>
</div> </div>

View 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">
<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>

View file

@ -3,7 +3,6 @@
<div class='item-container'> <div class='item-container'>
<div class="item-info"> <div class="item-info">
<h1 class='item-name'><input type='text' name='name' value='{{source.name}}' /></h1> <h1 class='item-name'><input type='text' name='name' value='{{source.name}}' /></h1>
<h2 class="label">Party</h2>
</div> </div>
</div> </div>
</header> </header>

View file

@ -9,33 +9,160 @@
<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 data-action="triggerRest" data-action="triggerRest" data-type="shortRest">
{{!-- <button> <i class="fa-solid fa-utensils"></i>
<i class="fa-solid fa-handshake-angle"></i> <span>{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}</span>
<span>Help Action</span> </button>
</button> --}} <button data-action="triggerRest" data-type="longRest">
<i class="fa-solid fa-bed"></i>
<span>{{localize "DAGGERHEART.APPLICATIONS.Downtime.longRest.title"}}</span>
</button>
</div> </div>
<fieldset class="actors-section glassy"> <ul class="actors-list">
<legend>{{localize tabs.partyMembers.label}}</legend> {{#each partyMembers as |member id|}}
<ul class="actors-list"> <li class="actor-resources">
{{#each document.system.partyMembers as |actor id|}} <div class="actor-img-frame">
{{> 'daggerheart.inventory-item' <img class="actor-img" src="{{member.img}}">
item=actor {{#if member.weapons}}
type='character' <div class="equipped-weapons">
isActor=true {{#each member.weapons as |weapon|}}
hideContextMenu=true <img src="{{weapon.img}}" data-tooltip="{{weapon.name}}"/>
}} {{/each}}
{{/each}} </div>
</ul> {{/if}}
{{#unless document.system.partyMembers.length}} {{#if member.evasion includeZero=true}}
<div class="actors-dragger"> <div class="evasion" data-tooltip="DAGGERHEART.GENERAL.evasion">{{member.evasion}}</div>
<span>{{localize "DAGGERHEART.GENERAL.dropActorsHere"}}</span> {{/if}}
</div> {{#if member.difficulty includeZero=true}}
{{/unless}} <div class="evasion" data-tooltip="DAGGERHEART.GENERAL.difficulty">{{member.difficulty}}</div>
</fieldset> {{/if}}
{{#unless (eq member.type 'companion')}}
<div class="threshold-section">
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.minor"}}</h4>
<h4 class="threshold-value">{{member.damageThresholds.major}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.major"}}</h4>
<h4 class="threshold-value">{{member.damageThresholds.severe}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}</h4>
</div>
{{/unless}}
</div>
<header>
<h2 class="actor-name">
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a>
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
</h2>
<div>
{{#unless (or (eq member.type 'companion') (eq member.type 'adversary')) }}
<div class="hope-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
{{#times member.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>
{{/if}}
</span>
{{/times}}
</div>
{{/unless}}
</div>
{{#if member.subtitle}}
<span class="subtitle">{{member.subtitle}}</span>
{{/if}}
</header>
<section class="body">
<section class="resources">
{{#unless (eq member.type 'companion') }}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.HitPoints.plural">
<span class="label">
<i class="fa-solid fa-heart" inert></i>
</span>
<span class="value">
<span class="current">{{member.resources.hitPoints.value}}</span>
/
<span class="max">{{member.resources.hitPoints.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.resources.hitPoints.max}}
<span class='slot {{#if (gte member.resources.hitPoints.value (add this 1))}}filled{{/if}}'
data-action='toggleHitPoints' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
</div>
{{/unless}}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.stress">
<span class="label">
<i class="fa-solid fa-bolt" inert></i>
</span>
<span class="value">
<span class="current">{{member.resources.stress.value}}</span>
/
<span class="max">{{member.resources.stress.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.resources.stress.max}}
<span class='slot {{#if (gte member.resources.stress.value (add this 1))}}filled{{/if}}'
data-action='toggleStress' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
</div>
{{#if member.armorScore.max}}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.armorSlots">
<span class="label">
<i class="fa-solid fa-shield" inert></i>
</span>
<span class="value">
<span class="current">{{member.armorScore.value}}</span>
/
<span class="max">{{member.armorScore.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.armorScore.max}}
<a class='armor-slot' data-action='toggleArmorSlot' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i>
{{else}}
<i class="fa-solid fa-shield-halved"></i>
{{/if}}
</a>
{{/times}}
</div>
</div>
{{/if}}
</section>
{{#if member.traits}}
<div class="traits">
{{#each member.traits as |trait|}}
<span class="trait">
<span class="label">{{trait.label}}</span>
<span class="value">{{trait.value}}</span>
</span>
{{/each}}
</div>
{{/if}}
</section>
</li>
{{/each}}
</ul>
{{#unless document.system.partyMembers.length}}
<div class="actors-dragger">
<span>{{localize "DAGGERHEART.GENERAL.dropActorsHere"}}</span>
</div>
{{/unless}}
</section> </section>

View file

@ -1,110 +0,0 @@
<section
class='tab {{tabs.resources.cssClass}} {{tabs.resources.id}}'
data-tab='{{tabs.resources.id}}'
data-group='{{tabs.resources.group}}'
>
<div data-action="triggerRest" data-type="longRest" class="actions-section">
<button data-type="longRest">
<i class="fa-solid fa-bed"></i>
<span>{{localize "DAGGERHEART.APPLICATIONS.Downtime.longRest.title"}}</span>
</button>
<button data-action="triggerRest" data-type="shortRest">
<i class="fa-solid fa-utensils"></i>
<span>{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}</span>
</button>
</div>
<fieldset class="resource-section glassy">
<legend>{{localize tabs.resources.label}}</legend>
<ul class="actors-list">
{{#each document.system.partyMembers as |actor id|}}
<li class="actor-resources">
<h2 class="actor-name">{{actor.name}}</h2>
<img class="actor-img" src="{{actor.img}}">
<div class="resources">
{{#unless (eq actor.type 'companion') }}
<div class="slot-section">
<div class="slot-bar">
{{#times actor.system.resources.hitPoints.max}}
<span class='slot {{#if (gte actor.system.resources.hitPoints.value (add this 1))}}filled{{/if}}'
data-action='toggleHitPoints' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
<div class="slot-label">
<span class="label">{{localize "DAGGERHEART.GENERAL.HitPoints.short"}}</span>
<span class="value">{{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}}</span>
</div>
</div>
{{/unless}}
<div class="slot-section">
<div class="slot-bar">
{{#times actor.system.resources.stress.max}}
<span class='slot {{#if (gte actor.system.resources.stress.value (add this 1))}}filled{{/if}}'
data-action='toggleStress' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
<div class="slot-label">
<span class="label">{{localize "DAGGERHEART.GENERAL.stress"}}</span>
<span class="value">{{actor.system.resources.stress.value}} / {{actor.system.resources.stress.max}}</span>
</div>
</div>
{{#if actor.system.armorScore.max}}
<div class="slot-section">
<div class="slot-bar">
{{#times actor.system.armorScore.max}}
<a class='armor-slot' data-action='toggleArmorSlot' data-actor-id="{{actor.id}}" data-value="{{add this 1}}">
{{#if (gte actor.system.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i>
{{else}}
<i class="fa-solid fa-shield-halved"></i>
{{/if}}
</a>
{{/times}}
</div>
<div class="slot-label">
<span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span>
<span class="value">{{actor.system.armorScore.value}} / {{actor.system.armorScore.max}}</span>
</div>
</div>
{{/if}}
{{#unless (or (eq actor.type 'companion') (eq actor.type 'adversary')) }}
<div class="hope-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
{{#times actor.system.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
{{#if (gte actor.system.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>
{{/if}}
</span>
{{/times}}
</div>
{{/unless}}
{{#if (eq actor.type 'character')}}
<div class="stat-section">
<h4>{{localize "DAGGERHEART.GENERAL.evasion"}}: {{actor.system.evasion}}</h4>
</div>
{{/if}}
{{#unless (eq actor.type 'companion')}}
<div class="threshold-section">
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}</h4>
<h4 class="threshold-value">{{actor.system.damageThresholds.major}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.major"}}</h4>
<h4 class="threshold-value">{{actor.system.damageThresholds.severe}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}</h4>
</div>
{{/unless}}
</div>
</li>
{{/each}}
</ul>
</fieldset>
</section>