Finished GroupRoll

This commit is contained in:
WBHarry 2025-11-05 23:51:14 +01:00
parent a442cb904b
commit c49079c57c
15 changed files with 632 additions and 42 deletions

View file

@ -123,7 +123,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
}
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
if (tagTeamSetting.members[this.actor.id]) {
if (tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
context.activeTagTeamRoll = true;
context.tagTeamSelected = this.config.tagTeamSelected;
}

View file

@ -19,6 +19,9 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
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,
@ -36,17 +39,20 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const changeChoices = this.actors;
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(changeChoices);
update(leaderChoices);
} else {
text = text.toLowerCase();
var suggestions = changeChoices.filter(n => n.name.toLowerCase().includes(text));
var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text));
update(suggestions);
}
},
@ -76,7 +82,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
},
onSelect: actor => {
element.value = actor.uuid;
this.actorLeader = { actor: actor, trait: '', difficulty: 0 };
this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 };
this.render();
},
click: e => e.fetch(),
@ -92,10 +98,10 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
input: element,
fetch: function (text, update) {
if (!text) {
update(changeChoices);
update(memberChoices);
} else {
text = text.toLowerCase();
var suggestions = changeChoices.filter(n => n.name.toLowerCase().includes(text));
var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text));
update(suggestions);
}
},
@ -125,7 +131,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
},
onSelect: actor => {
element.value = actor.uuid;
this.actorsMembers.push({ actor: actor, trait: '', difficulty: 0 });
this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 });
this.render({ force: true });
},
click: e => e.fetch(),
@ -140,14 +146,22 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.leader = this.actorLeader;
context.members = [...this.actorsMembers];
context.traitList = Object.values(abilities).map(trait => ({
label: game.i18n.localize(trait.label),
value: trait.id
}));
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();
@ -158,8 +172,25 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
this.render();
}
static async #roll(_, button) {
console.log(this.leader, this.members);
console.log(this);
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();
}
}

View file

@ -1,4 +1,5 @@
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
import { abilities } from '../../config/actorConfig.mjs';
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
constructor(options) {
@ -67,6 +68,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(event, data.message))
);
html.querySelectorAll('.group-roll-button').forEach(element =>
element.addEventListener('click', event => this.groupRollButton(event, data.message))
);
html.querySelectorAll('.group-roll-reroll').forEach(element =>
element.addEventListener('click', event => this.groupRollReroll(event, data.message))
);
html.querySelectorAll('.group-roll-success').forEach(element =>
element.addEventListener('click', event => this.groupRollSuccessEvent(event, data.message))
);
html.querySelectorAll('.group-roll-header-expand-section').forEach(element =>
element.addEventListener('click', this.groupRollExpandSection)
);
};
setupHooks() {
@ -176,4 +189,156 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
});
}
}
async groupRollButton(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,
resources: 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 renderData = { system: foundry.utils.deepClone(message.system) };
foundry.utils.setProperty(renderData.system, `${path}.result`, result.roll);
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
}
};
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 renderData = { system: foundry.utils.deepClone(message.system) };
foundry.utils.setProperty(renderData.system, `${path}.result`, { ...result.roll, rerolled: true });
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 renderData = { system: foundry.utils.deepClone(message.system) };
foundry.utils.setProperty(renderData.system, `${path}.manualSuccess`, Boolean(success));
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');
}
}

View file

@ -1,4 +1,4 @@
import { emitAsGM, GMUpdateEvent, socketEvent } from '../../systemRegistration/socket.mjs';
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;

View file

@ -1,5 +1,6 @@
import DHAbilityUse from './abilityUse.mjs';
import DHActorRoll from './actorRoll.mjs';
import DHGroupRoll from './groupRoll.mjs';
import DHSystemMessage from './systemMessage.mjs';
export const config = {
@ -7,5 +8,6 @@ export const config = {
adversaryRoll: DHActorRoll,
damageRoll: DHActorRoll,
dualityRoll: DHActorRoll,
groupRoll: DHGroupRoll,
systemMessage: DHSystemMessage
};

View file

@ -0,0 +1,31 @@
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))
};
}
}
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;
}
}

View file

@ -70,8 +70,11 @@ export default class DHRoll extends Roll {
if (Hooks.call(`${CONFIG.DH.id}.postRoll${hook.capitalize()}`, config, message) === false) return null;
}
// Create Chat Message
if (!config.source?.message) config.message = await this.toMessage(roll, config);
if (config.skips?.createMessage && game.modules.get('dice-so-nice')?.active) {
await game.dice3d.showForRoll(roll, game.user, true);
} else if (!config.source?.message) {
config.message = await this.toMessage(roll, config);
}
}
static postEvaluate(roll, config = {}) {
@ -237,7 +240,8 @@ export const registerRollDiceHooks = () => {
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
config.actionType === 'reaction' ||
config.tagTeamSelected
config.tagTeamSelected ||
config.skips?.resources
)
return;
const actor = await fromUuid(config.source.actor);

View file

@ -15,7 +15,8 @@ export default class RegisterHandlebarsHelpers {
setVar: this.setVar,
empty: this.empty,
pluralize: this.pluralize,
positive: this.positive
positive: this.positive,
isNullish: this.isNullish
});
}
static add(a, b) {
@ -94,4 +95,8 @@ export default class RegisterHandlebarsHelpers {
static positive(a) {
return Math.abs(Number(a));
}
static isNullish(a) {
return a === null || a === undefined;
}
}