mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
.
This commit is contained in:
parent
6c761b1840
commit
3ddfb7e75e
18 changed files with 1021 additions and 24 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { RefreshType } from '../../systemRegistration/socket.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -14,10 +14,17 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
...member.toObject(),
|
||||
uuid: member.uuid,
|
||||
id: member.id,
|
||||
selected: false,
|
||||
selected: true,
|
||||
owned: member.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
|
||||
}));
|
||||
|
||||
this.mainCharacter = null;
|
||||
this.openForAllPlayers = true;
|
||||
|
||||
this.tabGroups.application = Object.keys(party.system.groupRoll.participants).length
|
||||
? 'groupRoll'
|
||||
: 'initialization';
|
||||
|
||||
Hooks.on(socketEvent.Refresh, this.groupRollRefresh.bind());
|
||||
}
|
||||
|
||||
|
|
@ -30,54 +37,186 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
id: 'GroupRollDialog',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
|
||||
position: { width: 550, height: 'auto' },
|
||||
actions: {},
|
||||
actions: {
|
||||
toggleSelectMember: this.#toggleSelectMember,
|
||||
startGroupRoll: this.#startGroupRoll,
|
||||
makeRoll: this.#makeRoll,
|
||||
removeRoll: this.#removeRoll,
|
||||
rerollDice: this.#rerollDice,
|
||||
makeMainCharacterRoll: this.#makeMainCharacterRoll,
|
||||
removeMainCharacterRoll: this.#removeMainCharacterRoll,
|
||||
rerollMainCharacterDice: this.#rerollMainCharacterDice,
|
||||
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'
|
||||
},
|
||||
mainCharacter: {
|
||||
id: 'mainCharacter',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMainCharacter.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.updateMainCharacterField.bind(this));
|
||||
}
|
||||
|
||||
_configureRenderParts(options) {
|
||||
const { groupRoll } = super._configureRenderParts(options);
|
||||
const augmentedParts = { groupRoll };
|
||||
for (const memberKey of Object.keys(this.party.system.tagTeam.members)) {
|
||||
const { initialization, mainCharacter, 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/tagTeamDialog/tagTeamMember.hbs'
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMember.hbs'
|
||||
};
|
||||
}
|
||||
augmentedParts.rollSelection = rollSelection;
|
||||
augmentedParts.tagTeamRoll = tagTeamRoll;
|
||||
|
||||
augmentedParts.mainCharacter = mainCharacter;
|
||||
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;
|
||||
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.selectedMainCharacter = this.mainCharacter;
|
||||
partContext.selectedMainCharacterOptions = selectedMembers
|
||||
.filter(actor => actor.owned)
|
||||
.map(x => ({ value: x.id, label: x.name }));
|
||||
partContext.selectedMainCharacterDisabled = !selectedMembers.length;
|
||||
|
||||
partContext.canStartGroupRoll = selectedMembers.length > 1;
|
||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||
break;
|
||||
case 'mainCharacter':
|
||||
partContext.mainCharacter = this.getRollCharacterData(this.party.system.groupRoll.mainCharacter);
|
||||
break;
|
||||
case 'groupRoll':
|
||||
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 mainCharacterTotal = this.party.system.groupRoll.mainCharacter?.rollData
|
||||
? this.party.system.groupRoll.mainCharacter.roll.total
|
||||
: null;
|
||||
partContext.groupRoll = {
|
||||
totalLabel: this.party.system.groupRoll.mainCharacter?.rollData
|
||||
? game.i18n.format('DAGGERHEART.GENERAL.withThing', {
|
||||
thing: this.party.system.groupRoll.mainCharacter.roll.totalLabel
|
||||
})
|
||||
: null,
|
||||
total: mainCharacterTotal + modifierTotal,
|
||||
mainCharacterTotal,
|
||||
modifiers
|
||||
};
|
||||
break;
|
||||
case 'footer':
|
||||
partContext.canFinishRoll =
|
||||
Boolean(this.party.system.groupRoll.mainCharacter?.rollData) &&
|
||||
Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successfull !== null);
|
||||
break;
|
||||
}
|
||||
|
||||
if (Object.keys(this.party.system.tagTeam.members).includes(partId)) {
|
||||
const data = this.party.system.tagTeam.members[partId];
|
||||
const actor = game.actors.get(partId);
|
||||
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);
|
||||
|
||||
|
|
@ -93,7 +232,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
this.render({ parts: updatingParts });
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: { refreshType: RefreshType.TagTeamRoll, action: 'refresh', parts: updatingParts }
|
||||
data: { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts }
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -102,22 +241,38 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
gmUpdate,
|
||||
update,
|
||||
this.party.uuid,
|
||||
options.render
|
||||
? { refreshType: RefreshType.TagTeamRoll, action: 'refresh', parts: updatingParts }
|
||||
: undefined
|
||||
options.render ? { refreshType: RefreshType.GroupRoll, action: 'refresh', parts: updatingParts } : undefined
|
||||
);
|
||||
}
|
||||
|
||||
getUpdatingParts(target) {
|
||||
const { initialization, mainCharacter, groupRoll, footer } = this.constructor.PARTS;
|
||||
const isInitialization = this.tabGroups.application === initialization.id;
|
||||
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
|
||||
const updatingMainCharacter = target.closest('.main-character-outer-container');
|
||||
|
||||
return [...(updatingMember ? [updatingMember] : []), rollSelection.id];
|
||||
return [
|
||||
...(isInitialization ? [initialization.id] : []),
|
||||
...(updatingMember ? [updatingMember] : []),
|
||||
...(updatingMainCharacter ? [mainCharacter.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;
|
||||
|
|
@ -134,4 +289,235 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
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();
|
||||
}
|
||||
|
||||
updateMainCharacterField(event) {
|
||||
if (!this.mainCharacter) this.mainCharacter = {};
|
||||
this.mainCharacter.memberId = event.target.value;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #startGroupRoll() {
|
||||
const mainCharacter = this.partyMembers.find(x => x.id === this.mainCharacter.memberId);
|
||||
const aidingCharacters = this.partyMembers.reduce((acc, curr) => {
|
||||
if (curr.selected && curr.id !== this.mainCharacter.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(),
|
||||
mainCharacter: { id: mainCharacter.id, name: mainCharacter.name, img: mainCharacter.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
|
||||
|
||||
static async #makeRoll(_event, button) {
|
||||
const { member } = button.dataset;
|
||||
|
||||
const actor = game.actors.find(x => x.id === member);
|
||||
if (!actor) return;
|
||||
|
||||
const characterData = this.party.system.groupRoll.aidingCharacters[member];
|
||||
const result = await actor.rollTrait(characterData.rollChoice, {
|
||||
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.aidingCharacters.${member}.rollData`]: rollData
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
static async #removeRoll(_, button) {
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.groupRoll.aidingCharacters.${button.dataset.member}`]: {
|
||||
rollData: null,
|
||||
rollChoice: null,
|
||||
selected: false
|
||||
}
|
||||
},
|
||||
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 #rerollMainCharacterDice(_, button) {
|
||||
this.rerollDice(button, this.party.system.groupRoll.mainCharacter, `system.groupRoll.mainCharacter.rollData`);
|
||||
}
|
||||
|
||||
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 #makeMainCharacterRoll(_event, button) {
|
||||
const actor = game.actors.find(x => x.id === this.party.system.groupRoll.mainCharacter.id);
|
||||
if (!actor) return;
|
||||
|
||||
const characterData = this.party.system.groupRoll.mainCharacter;
|
||||
const result = await actor.rollTrait(characterData.rollChoice, {
|
||||
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) {
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.groupRoll.mainCharacter`]: {
|
||||
rollData: null,
|
||||
rollChoice: null,
|
||||
selected: false
|
||||
}
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
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': {
|
||||
mainCharacter: 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.mainCharacter.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.mainCharacter.id);
|
||||
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: 'dualityRoll',
|
||||
user: game.user.id,
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||
speaker: cls.getSpeaker({ actor }),
|
||||
system: systemData,
|
||||
rolls: [JSON.stringify(totalRoll)],
|
||||
sound: null,
|
||||
flags: { core: { RollTable: true } }
|
||||
};
|
||||
|
||||
await cls.create(msgData);
|
||||
|
||||
/* Fin */
|
||||
this.cancelRoll({ confirm: false });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue