mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
[PR] [Feature] Party Sheet (#1230)
* start development * finish party members tab * start resources tab * finish resources tab * finish inventory tab and add inital template to projects tab * add resource buttons actions methods * add group roll dialog * Main implementation * Fixed costs * Minor fixes and tweaks for the party sheet (#1239) * Minor fixes and tweaks for the party sheet * Fix scroll restoration for party sheet tabs * Finished GroupRoll * Removed/commented-out not yet implemented things * Commented out Difficulty since it's not used yet * Re-render party when members update (#1242) * Fixed so style applies in preview chat message * Added the clown car * Fixed so items can be dropped into the Party sheet * Added delete icon to inventory * Fixed TokenHUD token property useage. Fixed skipping roll message * Added visible modifier to GroupRoll leader result * Leader roll displays the large result display right away after rolling * Corrected tokenHUD for non-player-tokens * Fixed clowncar tokenData * Fixed TagTeam roll message and sound * Removed final TagTeamRoll roll sound * [PR] [Party Sheets] Sidebar character sheet changes (#1249) * Something experimenting * I am silly (wearning Dunce hat) * Stressful task * Armor functional to be hit * CSS Changes to accomadate pip boy * last minute change to resource section for better visual feeling * restoring old css for toggle * Added setting to toggle pip/number display * toggle functionality added * Fixed light-mode in characterSheet * Fixed multi-row resource pips display for character * Fixed separators * Added pip-display to Adversary and Companion. Some fixing on armor display --------- Co-authored-by: WBHarry <williambjrklund@gmail.com> * Fixed party height and resource armor update * Fixed deletebutton padding * Only showing expand-me icon on InventoryItem if there is a description to show * . * Fixed menu icon to be beige instead of white in dark mode --------- Co-authored-by: moliloo <dev.murilobrito@gmail.com> Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com> Co-authored-by: Nikhil Nagarajan <potter.nikhil@gmail.com>
This commit is contained in:
parent
f53252a369
commit
261a3a68b0
91 changed files with 3769 additions and 271 deletions
|
|
@ -11,3 +11,5 @@ export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
|||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
export { default as GroupRollDialog } from './group-roll-dialog.mjs';
|
||||
export { default as TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
updateIsAdvantage: this.updateIsAdvantage,
|
||||
selectExperience: this.selectExperience,
|
||||
toggleReaction: this.toggleReaction,
|
||||
toggleTagTeamRoll: this.toggleTagTeamRoll,
|
||||
submitRoll: this.submitRoll
|
||||
},
|
||||
form: {
|
||||
|
|
@ -120,6 +121,13 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
||||
context.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
|
||||
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
if (tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
|
||||
context.activeTagTeamRoll = true;
|
||||
context.tagTeamSelected = this.config.tagTeamSelected;
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -195,6 +203,11 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
}
|
||||
|
||||
static toggleTagTeamRoll() {
|
||||
this.config.tagTeamSelected = !this.config.tagTeamSelected;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async submitRoll() {
|
||||
await this.close({ submitted: true });
|
||||
}
|
||||
|
|
|
|||
196
module/applications/dialogs/group-roll-dialog.mjs
Normal file
196
module/applications/dialogs/group-roll-dialog.mjs
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import autocomplete from 'autocompleter';
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(actors) {
|
||||
super();
|
||||
this.actors = actors;
|
||||
this.actorLeader = {};
|
||||
this.actorsMembers = [];
|
||||
}
|
||||
|
||||
get title() {
|
||||
return 'Group Roll';
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll'],
|
||||
position: { width: 'auto', height: 'auto' },
|
||||
window: {
|
||||
title: 'DAGGERHEART.UI.Chat.groupRoll.title'
|
||||
},
|
||||
actions: {
|
||||
roll: GroupRollDialog.#roll,
|
||||
removeLeader: GroupRollDialog.#removeLeader,
|
||||
removeMember: GroupRollDialog.#removeMember
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
application: {
|
||||
id: 'group-roll',
|
||||
template: 'systems/daggerheart/templates/dialogs/group-roll/group-roll.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
const leaderChoices = this.actors.filter(x => this.actorsMembers.every(member => member.actor?.id !== x.id));
|
||||
const memberChoices = this.actors.filter(
|
||||
x => this.actorLeader?.actor?.id !== x.id && this.actorsMembers.every(member => member.actor?.id !== x.id)
|
||||
);
|
||||
|
||||
htmlElement.querySelectorAll('.leader-change-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(leaderChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = leaderChoices.filter(n => n.name.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (actor, search) {
|
||||
const actorName = game.i18n.localize(actor.name);
|
||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = actorName.slice(0, matchIndex);
|
||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
||||
const img = document.createElement('img');
|
||||
img.src = actor.img;
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.appendChild(img);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
element.appendChild(label);
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: actor => {
|
||||
element.value = actor.uuid;
|
||||
this.actorLeader = { actor: actor, trait: 'agility', difficulty: 0 };
|
||||
this.render();
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
|
||||
htmlElement.querySelectorAll('.team-push-input').forEach(element => {
|
||||
autocomplete({
|
||||
input: element,
|
||||
fetch: function (text, update) {
|
||||
if (!text) {
|
||||
update(memberChoices);
|
||||
} else {
|
||||
text = text.toLowerCase();
|
||||
var suggestions = memberChoices.filter(n => n.name.toLowerCase().includes(text));
|
||||
update(suggestions);
|
||||
}
|
||||
},
|
||||
render: function (actor, search) {
|
||||
const actorName = game.i18n.localize(actor.name);
|
||||
const matchIndex = actorName.toLowerCase().indexOf(search);
|
||||
|
||||
const beforeText = actorName.slice(0, matchIndex);
|
||||
const matchText = actorName.slice(matchIndex, matchIndex + search.length);
|
||||
const after = actorName.slice(matchIndex + search.length, actorName.length);
|
||||
const img = document.createElement('img');
|
||||
img.src = actor.img;
|
||||
|
||||
const element = document.createElement('li');
|
||||
element.appendChild(img);
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.innerHTML = `${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`;
|
||||
element.appendChild(label);
|
||||
|
||||
return element;
|
||||
},
|
||||
renderGroup: function (label) {
|
||||
const itemElement = document.createElement('div');
|
||||
itemElement.textContent = game.i18n.localize(label);
|
||||
return itemElement;
|
||||
},
|
||||
onSelect: actor => {
|
||||
element.value = actor.uuid;
|
||||
this.actorsMembers.push({ actor: actor, trait: 'agility', difficulty: 0 });
|
||||
this.render({ force: true });
|
||||
},
|
||||
click: e => e.fetch(),
|
||||
customize: function (_input, _inputRect, container) {
|
||||
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
|
||||
},
|
||||
minLength: 0
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.leader = this.actorLeader;
|
||||
context.members = this.actorsMembers;
|
||||
context.traitList = abilities;
|
||||
|
||||
context.allSelected = this.actorsMembers.length + (this.actorLeader?.actor ? 1 : 0) === this.actors.length;
|
||||
context.rollDisabled = context.members.length === 0 || !this.actorLeader?.actor;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static updateData(event, _, formData) {
|
||||
const { actorLeader, actorsMembers } = foundry.utils.expandObject(formData.object);
|
||||
this.actorLeader = foundry.utils.mergeObject(this.actorLeader, actorLeader);
|
||||
this.actorsMembers = foundry.utils.mergeObject(this.actorsMembers, actorsMembers);
|
||||
this.render(true);
|
||||
}
|
||||
|
||||
static async #removeLeader(_, button) {
|
||||
this.actorLeader = null;
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #removeMember(_, button) {
|
||||
this.actorsMembers = this.actorsMembers.filter(m => m.actor.uuid !== button.dataset.memberUuid);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #roll() {
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const systemData = {
|
||||
leader: this.actorLeader,
|
||||
members: this.actorsMembers
|
||||
};
|
||||
const msg = {
|
||||
type: 'groupRoll',
|
||||
user: game.user.id,
|
||||
speaker: cls.getSpeaker(),
|
||||
title: game.i18n.localize('DAGGERHEART.UI.Chat.groupRoll.title'),
|
||||
system: systemData,
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
||||
{ system: systemData }
|
||||
)
|
||||
};
|
||||
|
||||
cls.create(msg);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -122,6 +124,15 @@ export default class RerollDamageDialog extends HandlebarsApplicationMixin(Appli
|
|||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
|
||||
await this.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
315
module/applications/dialogs/tagTeamDialog.mjs
Normal file
315
module/applications/dialogs/tagTeamDialog.mjs
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
import { GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(party) {
|
||||
super();
|
||||
|
||||
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
this.party = party;
|
||||
|
||||
this.setupHooks = Hooks.on(socketEvent.Refresh, ({ refreshType }) => {
|
||||
if (refreshType === RefreshType.TagTeamRoll) {
|
||||
this.data = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title');
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
||||
position: { width: 550, height: 'auto' },
|
||||
actions: {
|
||||
removeMember: TagTeamDialog.#removeMember,
|
||||
unlinkMessage: TagTeamDialog.#unlinkMessage,
|
||||
selectMessage: TagTeamDialog.#selectMessage,
|
||||
createTagTeam: TagTeamDialog.#createTagTeam
|
||||
},
|
||||
form: { handler: this.updateData, submitOnChange: true, closeOnSubmit: false }
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
application: {
|
||||
id: 'tag-team-dialog',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.hopeCost = this.hopeCost;
|
||||
context.data = this.data;
|
||||
|
||||
context.memberOptions = this.party.filter(c => !this.data.members[c.id]);
|
||||
context.selectedCharacterOptions = this.party.filter(c => this.data.members[c.id]);
|
||||
|
||||
context.members = Object.keys(this.data.members).map(id => {
|
||||
const roll = this.data.members[id].messageId ? game.messages.get(this.data.members[id].messageId) : null;
|
||||
|
||||
context.usesDamage =
|
||||
context.usesDamage === undefined
|
||||
? roll?.system.hasDamage
|
||||
: context.usesDamage && roll?.system.hasDamage;
|
||||
return {
|
||||
character: this.party.find(x => x.id === id),
|
||||
selected: this.data.members[id].selected,
|
||||
roll: roll,
|
||||
damageValues: roll
|
||||
? Object.keys(roll.system.damage).map(key => ({
|
||||
key: key,
|
||||
name: game.i18n.localize(CONFIG.DH.GENERAL.healingTypes[key].label),
|
||||
total: roll.system.damage[key].total
|
||||
}))
|
||||
: null
|
||||
};
|
||||
});
|
||||
|
||||
const initiatorChar = this.party.find(x => x.id === this.data.initiator.id);
|
||||
context.initiator = {
|
||||
character: initiatorChar,
|
||||
cost: this.data.initiator.cost
|
||||
};
|
||||
|
||||
context.selectedData = Object.values(context.members).reduce(
|
||||
(acc, member) => {
|
||||
if (!member.roll) return acc;
|
||||
if (member.selected) {
|
||||
acc.result = `${member.roll.system.roll.total} ${member.roll.system.roll.result.label}`;
|
||||
}
|
||||
|
||||
if (context.usesDamage) {
|
||||
if (!acc.damageValues) acc.damageValues = {};
|
||||
for (let damage of member.damageValues) {
|
||||
if (acc.damageValues[damage.key]) {
|
||||
acc.damageValues[damage.key].total += damage.total;
|
||||
} else {
|
||||
acc.damageValues[damage.key] = foundry.utils.deepClone(damage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ result: null, damageValues: null }
|
||||
);
|
||||
context.showResult = Object.values(context.members).reduce((enabled, member) => {
|
||||
if (!member.roll) return enabled;
|
||||
if (context.usesDamage) {
|
||||
enabled = enabled === null ? member.damageValues.length > 0 : enabled && member.damageValues.length > 0;
|
||||
} else {
|
||||
enabled = enabled === null ? Boolean(member.roll) : enabled && Boolean(member.roll);
|
||||
}
|
||||
|
||||
return enabled;
|
||||
}, null);
|
||||
|
||||
context.createDisabled =
|
||||
!context.selectedData.result ||
|
||||
!this.data.initiator.id ||
|
||||
Object.keys(this.data.members).length === 0 ||
|
||||
Object.values(context.members).some(x =>
|
||||
context.usesDamage ? !x.damageValues || x.damageValues.length === 0 : !x.roll
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async updateSource(update) {
|
||||
await this.data.updateSource(update);
|
||||
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, this.data.toObject());
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||
update: this.data.toObject(),
|
||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static async updateData(_event, _element, formData) {
|
||||
const { selectedAddMember, initiator } = foundry.utils.expandObject(formData.object);
|
||||
const update = { initiator: initiator };
|
||||
if (selectedAddMember) {
|
||||
const member = await foundry.utils.fromUuid(selectedAddMember);
|
||||
update[`members.${member.id}`] = { messageId: null };
|
||||
}
|
||||
|
||||
await this.updateSource(update);
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #removeMember(_, button) {
|
||||
const update = { [`members.-=${button.dataset.characterId}`]: null };
|
||||
if (this.data.initiator.id === button.dataset.characterId) {
|
||||
update.iniator = { id: null };
|
||||
}
|
||||
|
||||
await this.updateSource(update);
|
||||
}
|
||||
|
||||
static async #unlinkMessage(_, button) {
|
||||
await this.updateSource({ [`members.${button.id}.messageId`]: null });
|
||||
}
|
||||
|
||||
static async #selectMessage(_, button) {
|
||||
const member = this.data.members[button.id];
|
||||
const currentSelected = Object.keys(this.data.members).find(key => this.data.members[key].selected);
|
||||
const curretSelectedUpdate =
|
||||
currentSelected && currentSelected !== button.id ? { [`${currentSelected}`]: { selected: false } } : {};
|
||||
await this.updateSource({
|
||||
members: {
|
||||
[`${button.id}`]: { selected: !member.selected },
|
||||
...curretSelectedUpdate
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async #createTagTeam() {
|
||||
const mainRollId = Object.keys(this.data.members).find(key => this.data.members[key].selected);
|
||||
const mainRoll = game.messages.get(this.data.members[mainRollId].messageId);
|
||||
|
||||
if (this.data.initiator.cost) {
|
||||
const initiator = this.party.find(x => x.id === this.data.initiator.id);
|
||||
if (initiator.system.resources.hope.value < this.data.initiator.cost) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.insufficientHope')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const secondaryRolls = Object.keys(this.data.members)
|
||||
.filter(key => key !== mainRollId)
|
||||
.map(key => game.messages.get(this.data.members[key].messageId));
|
||||
|
||||
const systemData = foundry.utils.deepClone(mainRoll).system.toObject();
|
||||
for (let roll of secondaryRolls) {
|
||||
if (roll.system.hasDamage) {
|
||||
for (let key in roll.system.damage) {
|
||||
var damage = roll.system.damage[key];
|
||||
if (systemData.damage[key]) {
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts = [...systemData.damage[key].parts, ...damage.parts];
|
||||
} else {
|
||||
systemData.damage[key] = damage;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
systemData.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle');
|
||||
|
||||
const cls = getDocumentClass('ChatMessage'),
|
||||
msgData = {
|
||||
type: 'dualityRoll',
|
||||
user: game.user.id,
|
||||
title: game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.title'),
|
||||
speaker: cls.getSpeaker({ actor: this.party.find(x => x.id === mainRollId) }),
|
||||
system: systemData,
|
||||
rolls: mainRoll.rolls,
|
||||
sound: null,
|
||||
flags: { core: { RollTable: true } }
|
||||
};
|
||||
|
||||
await cls.create(msgData);
|
||||
|
||||
const fearUpdate = { key: 'fear', value: null, total: null, enabled: true };
|
||||
for (let memberId of Object.keys(this.data.members)) {
|
||||
const resourceUpdates = [];
|
||||
if (systemData.roll.isCritical || systemData.roll.result.duality === 1) {
|
||||
const value =
|
||||
memberId !== this.data.initiator.id
|
||||
? 1
|
||||
: this.data.initiator.cost
|
||||
? 1 - this.data.initiator.cost
|
||||
: 1;
|
||||
resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true });
|
||||
}
|
||||
if (systemData.roll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true });
|
||||
if (systemData.roll.result.duality === -1) {
|
||||
fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1;
|
||||
fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1;
|
||||
}
|
||||
|
||||
this.party.find(x => x.id === memberId).modifyResource(resourceUpdates);
|
||||
}
|
||||
|
||||
if (fearUpdate.value) {
|
||||
this.party.find(x => x.id === mainRollId).modifyResource([fearUpdate]);
|
||||
}
|
||||
|
||||
/* Improve by fetching default from schema */
|
||||
const update = { members: [], initiator: { id: null, cost: 3 } };
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, update);
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||
update: update,
|
||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static async assignRoll(char, message) {
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
|
||||
const character = settings.members[char.id];
|
||||
if (!character) return;
|
||||
|
||||
await settings.updateSource({ [`members.${char.id}.messageId`]: message.id });
|
||||
|
||||
if (game.user.isGM) {
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll, settings);
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateSetting,
|
||||
uuid: CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll,
|
||||
update: settings,
|
||||
refresh: { refreshType: RefreshType.TagTeamRoll }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async close(options = {}) {
|
||||
Hooks.off(socketEvent.Refresh, this.setupHooks);
|
||||
await super.close(options);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import { shuffleArray } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['daggerheart'],
|
||||
actions: {
|
||||
combat: DHTokenHUD.#onToggleCombat
|
||||
combat: DHTokenHUD.#onToggleCombat,
|
||||
togglePartyTokens: DHTokenHUD.#togglePartyTokens
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -19,6 +22,12 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
||||
context.partyOnCanvas =
|
||||
this.actor.type === 'party' &&
|
||||
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
|
||||
context.icons.toggleParty = 'systems/daggerheart/assets/icons/arrow-dunk.png';
|
||||
context.actorType = this.actor.type;
|
||||
context.usesEffects = this.actor.type !== 'party';
|
||||
context.canToggleCombat = DHTokenHUD.#nonCombatTypes.includes(this.actor.type)
|
||||
? false
|
||||
: context.canToggleCombat;
|
||||
|
|
@ -59,6 +68,105 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
}
|
||||
}
|
||||
|
||||
static async #togglePartyTokens(_, button) {
|
||||
const icon = button.querySelector('img');
|
||||
icon.classList.toggle('flipped');
|
||||
button.dataset.tooltip = game.i18n.localize(
|
||||
icon.classList.contains('flipped')
|
||||
? 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.retrievePartyTokens'
|
||||
: 'DAGGERHEART.APPLICATIONS.HUD.tokenHUD.depositPartyTokens'
|
||||
);
|
||||
|
||||
const animationDuration = 500;
|
||||
const activeTokens = this.actor.system.partyMembers.flatMap(member => member.getActiveTokens());
|
||||
const { x: actorX, y: actorY } = this.document;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
await token.document.update(
|
||||
{ x: actorX, y: actorY, alpha: 0 },
|
||||
{ animation: { duration: animationDuration } }
|
||||
);
|
||||
setTimeout(() => token.document.delete(), animationDuration);
|
||||
}
|
||||
} else {
|
||||
const activeScene = game.scenes.find(x => x.active);
|
||||
const partyTokenData = [];
|
||||
for (let member of this.actor.system.partyMembers) {
|
||||
const data = await member.getTokenDocument();
|
||||
partyTokenData.push(data.toObject());
|
||||
}
|
||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||
'Token',
|
||||
partyTokenData.map(tokenData => ({
|
||||
...tokenData,
|
||||
alpha: 0,
|
||||
x: actorX,
|
||||
y: actorY
|
||||
}))
|
||||
);
|
||||
|
||||
const { sizeX, sizeY } = activeScene.grid;
|
||||
const nrRandomPositions = Math.ceil(newTokens.length / 8) * 8;
|
||||
/* This is an overcomplicated mess, but I'm stupid */
|
||||
const positions = shuffleArray(
|
||||
[...Array(nrRandomPositions).keys()].map((_, index) => {
|
||||
const nonZeroIndex = index + 1;
|
||||
const indexFloor = Math.floor(index / 8);
|
||||
const distanceCoefficient = indexFloor + 1;
|
||||
const side = 3 + indexFloor * 2;
|
||||
const sideMiddle = Math.ceil(side / 2);
|
||||
const inbetween = 1 + indexFloor * 2;
|
||||
const inbetweenMiddle = Math.ceil(inbetween / 2);
|
||||
|
||||
if (index < side) {
|
||||
const distance =
|
||||
nonZeroIndex === sideMiddle
|
||||
? 0
|
||||
: nonZeroIndex < sideMiddle
|
||||
? -nonZeroIndex
|
||||
: nonZeroIndex - sideMiddle;
|
||||
return { x: actorX - sizeX * distance, y: actorY - sizeY * distanceCoefficient };
|
||||
} else if (index < side + inbetween) {
|
||||
const inbetweenIndex = nonZeroIndex - side;
|
||||
const distance =
|
||||
inbetweenIndex === inbetweenMiddle
|
||||
? 0
|
||||
: inbetweenIndex < inbetweenMiddle
|
||||
? -inbetweenIndex
|
||||
: inbetweenIndex - inbetweenMiddle;
|
||||
return { x: actorX + sizeX * distanceCoefficient, y: actorY + sizeY * distance };
|
||||
} else if (index < 2 * side + inbetween) {
|
||||
const sideIndex = nonZeroIndex - side - inbetween;
|
||||
const distance =
|
||||
sideIndex === sideMiddle
|
||||
? 0
|
||||
: sideIndex < sideMiddle
|
||||
? sideIndex
|
||||
: -(sideIndex - sideMiddle);
|
||||
return { x: actorX + sizeX * distance, y: actorY + sizeY * distanceCoefficient };
|
||||
} else {
|
||||
const inbetweenIndex = nonZeroIndex - 2 * side - inbetween;
|
||||
const distance =
|
||||
inbetweenIndex === inbetweenMiddle
|
||||
? 0
|
||||
: inbetweenIndex < inbetweenMiddle
|
||||
? inbetweenIndex
|
||||
: -(inbetweenIndex - inbetweenMiddle);
|
||||
return { x: actorX - sizeX * distanceCoefficient, y: actorY + sizeY * distance };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
for (let token of newTokens) {
|
||||
const position = positions.pop();
|
||||
token.update(
|
||||
{ x: position.x, y: position.y, alpha: 1 },
|
||||
{ animation: { duration: animationDuration } }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_getStatusEffectChoices() {
|
||||
// Include all HUD-enabled status effects
|
||||
const choices = {};
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ export { default as Adversary } from './adversary.mjs';
|
|||
export { default as Character } from './character.mjs';
|
||||
export { default as Companion } from './companion.mjs';
|
||||
export { default as Environment } from './environment.mjs';
|
||||
export { default as Party } from './party.mjs';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
position: { width: 660, height: 766 },
|
||||
window: { resizable: true },
|
||||
actions: {
|
||||
toggleHitPoints: AdversarySheet.#toggleHitPoints,
|
||||
toggleStress: AdversarySheet.#toggleStress,
|
||||
reactionRoll: AdversarySheet.#reactionRoll,
|
||||
toggleResourceDice: AdversarySheet.#toggleResourceDice,
|
||||
handleResourceDice: AdversarySheet.#handleResourceDice
|
||||
|
|
@ -75,6 +77,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
const context = await super._prepareContext(options);
|
||||
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
||||
|
||||
context.resources = Object.keys(this.document.system.resources).reduce((acc, key) => {
|
||||
acc[key] = this.document.system.resources[key];
|
||||
return acc;
|
||||
}, {});
|
||||
const maxResource = Math.max(context.resources.hitPoints.max, context.resources.stress.max);
|
||||
context.resources.hitPoints.emptyPips =
|
||||
context.resources.hitPoints.max < maxResource ? maxResource - context.resources.hitPoints.max : 0;
|
||||
context.resources.stress.emptyPips =
|
||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +167,27 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Toggles hitpoint resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleHitPoints(_, button) {
|
||||
const hitPointsValue = Number.parseInt(button.dataset.value);
|
||||
const newValue =
|
||||
this.document.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||
await this.document.update({ 'system.resources.hitPoints.value': newValue });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles stress resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleStress(_, button) {
|
||||
const StressValue = Number.parseInt(button.dataset.value);
|
||||
const newValue = this.document.system.resources.stress.value >= StressValue ? StressValue - 1 : StressValue;
|
||||
await this.document.update({ 'system.resources.stress.value': newValue });
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a reaction roll for an Adversary.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
actions: {
|
||||
toggleVault: CharacterSheet.#toggleVault,
|
||||
rollAttribute: CharacterSheet.#rollAttribute,
|
||||
toggleHitPoints: CharacterSheet.#toggleHitPoints,
|
||||
toggleStress: CharacterSheet.#toggleStress,
|
||||
toggleArmor: CharacterSheet.#toggleArmor,
|
||||
toggleHope: CharacterSheet.#toggleHope,
|
||||
toggleLoadoutView: CharacterSheet.#toggleLoadoutView,
|
||||
openPack: CharacterSheet.#openPack,
|
||||
|
|
@ -196,6 +199,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
context.resources = Object.keys(this.document.system.resources).reduce((acc, key) => {
|
||||
acc[key] = this.document.system.resources[key];
|
||||
return acc;
|
||||
}, {});
|
||||
const maxResource = Math.max(context.resources.hitPoints.max, context.resources.stress.max);
|
||||
context.resources.hitPoints.emptyPips =
|
||||
context.resources.hitPoints.max < maxResource ? maxResource - context.resources.hitPoints.max : 0;
|
||||
context.resources.stress.emptyPips =
|
||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||
|
||||
context.inventory = {
|
||||
currency: {
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
|
||||
|
|
@ -746,6 +759,37 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles hitpoint resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleHitPoints(_, button) {
|
||||
const hitPointsValue = Number.parseInt(button.dataset.value);
|
||||
const newValue =
|
||||
this.document.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||
await this.document.update({ 'system.resources.hitPoints.value': newValue });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles stress resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleStress(_, button) {
|
||||
const StressValue = Number.parseInt(button.dataset.value);
|
||||
const newValue = this.document.system.resources.stress.value >= StressValue ? StressValue - 1 : StressValue;
|
||||
await this.document.update({ 'system.resources.stress.value': newValue });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles ArmorScore resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleArmor(_, button, element) {
|
||||
const ArmorValue = Number.parseInt(button.dataset.value);
|
||||
const newValue = this.document.system.armor.system.marks.value >= ArmorValue ? ArmorValue - 1 : ArmorValue;
|
||||
await this.document.system.armor.update({ 'system.marks.value': newValue });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a hope resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
classes: ['actor', 'companion'],
|
||||
position: { width: 340 },
|
||||
actions: {
|
||||
toggleStress: DhCompanionSheet.#toggleStress,
|
||||
actionRoll: DhCompanionSheet.#actionRoll,
|
||||
levelManagement: DhCompanionSheet.#levelManagement
|
||||
}
|
||||
|
|
@ -50,6 +51,16 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Toggles stress resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleStress(_, button) {
|
||||
const StressValue = Number.parseInt(button.dataset.value);
|
||||
const newValue = this.document.system.resources.stress.value >= StressValue ? StressValue - 1 : StressValue;
|
||||
await this.document.update({ 'system.resources.stress.value': newValue });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -143,7 +143,6 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* Toggle the used state of a resource dice.
|
||||
* @type {ApplicationClickAction}
|
||||
|
|
@ -177,5 +176,4 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
|||
}, {})
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
512
module/applications/sheets/actors/party.mjs
Normal file
512
module/applications/sheets/actors/party.mjs
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
import { getDocFromElement } from '../../../helpers/utils.mjs';
|
||||
import { ItemBrowser } from '../../ui/itemBrowser.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs';
|
||||
import { socketEvent } from '../../../systemRegistration/socket.mjs';
|
||||
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
|
||||
import DhpActor from '../../../documents/actor.mjs';
|
||||
import DHItem from '../../../documents/item.mjs';
|
||||
|
||||
export default class Party extends DHBaseActorSheet {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['party'],
|
||||
position: {
|
||||
width: 550
|
||||
},
|
||||
window: {
|
||||
resizable: true
|
||||
},
|
||||
actions: {
|
||||
deletePartyMember: Party.#deletePartyMember,
|
||||
deleteItem: Party.#deleteItem,
|
||||
toggleHope: Party.#toggleHope,
|
||||
toggleHitPoints: Party.#toggleHitPoints,
|
||||
toggleStress: Party.#toggleStress,
|
||||
toggleArmorSlot: Party.#toggleArmorSlot,
|
||||
tempBrowser: Party.#tempBrowser,
|
||||
refeshActions: Party.#refeshActions,
|
||||
triggerRest: Party.#triggerRest,
|
||||
tagTeamRoll: Party.#tagTeamRoll,
|
||||
groupRoll: Party.#groupRoll,
|
||||
selectRefreshable: DaggerheartMenu.selectRefreshable,
|
||||
refreshActors: DaggerheartMenu.refreshActors
|
||||
},
|
||||
dragDrop: [{ dragSelector: '.actors-section .inventory-item', dropSelector: null }]
|
||||
};
|
||||
|
||||
/**@override */
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/party/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.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 */
|
||||
// projects: {
|
||||
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
||||
// scrollable: ['']
|
||||
// },
|
||||
inventory: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/party/inventory.hbs',
|
||||
scrollable: ['.tab.inventory .items-section']
|
||||
},
|
||||
notes: { template: 'systems/daggerheart/templates/sheets/actors/party/notes.hbs' }
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [
|
||||
{ id: 'partyMembers' },
|
||||
{ id: 'resources' },
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// { id: 'projects' },
|
||||
{ id: 'inventory' },
|
||||
{ id: 'notes' }
|
||||
],
|
||||
initial: 'partyMembers',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
this._createFilterMenus();
|
||||
this._createSearchFilter();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Prepare Context */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.inventory = {
|
||||
currency: {
|
||||
title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
|
||||
coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
|
||||
handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
|
||||
bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
|
||||
chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
|
||||
}
|
||||
};
|
||||
|
||||
const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency;
|
||||
if (homebrewCurrency.enabled) {
|
||||
context.inventory.currency = homebrewCurrency;
|
||||
}
|
||||
|
||||
if (context.inventory.length === 0) {
|
||||
context.inventory = Array(1).fill(Array(5).fill([]));
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'notes':
|
||||
await this._prepareNotesContext(context, options);
|
||||
break;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Header part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareHeaderContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
context.description = await TextEditor.implementation.enrichHTML(system.description, {
|
||||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare render context for the Biography part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareNotesContext(context, _options) {
|
||||
const { system } = this.document;
|
||||
const { TextEditor } = foundry.applications.ux;
|
||||
|
||||
const paths = {
|
||||
notes: 'notes'
|
||||
};
|
||||
|
||||
for (const [key, path] of Object.entries(paths)) {
|
||||
const value = foundry.utils.getProperty(system, path);
|
||||
context[key] = {
|
||||
field: system.schema.getField(path),
|
||||
value,
|
||||
enriched: await TextEditor.implementation.enrichHTML(value, {
|
||||
secrets: this.document.isOwner,
|
||||
relativeTo: this.document
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a hope resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleHope(_, target) {
|
||||
const hopeValue = Number.parseInt(target.dataset.value);
|
||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||
const newValue = actor.system.resources.hope.value >= hopeValue ? hopeValue - 1 : hopeValue;
|
||||
await actor.update({ 'system.resources.hope.value': newValue });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a hp resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleHitPoints(_, target) {
|
||||
const hitPointsValue = Number.parseInt(target.dataset.value);
|
||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||
const newValue = actor.system.resources.hitPoints.value >= hitPointsValue ? hitPointsValue - 1 : hitPointsValue;
|
||||
await actor.update({ 'system.resources.hitPoints.value': newValue });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a stress resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleStress(_, target) {
|
||||
const stressValue = Number.parseInt(target.dataset.value);
|
||||
const actor = await foundry.utils.fromUuid(target.dataset.actorId);
|
||||
const newValue = actor.system.resources.stress.value >= stressValue ? stressValue - 1 : stressValue;
|
||||
await actor.update({ 'system.resources.stress.value': newValue });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles a armor slot resource value.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #toggleArmorSlot(_, target, element) {
|
||||
const armorItem = await foundry.utils.fromUuid(target.dataset.itemUuid);
|
||||
const armorValue = Number.parseInt(target.dataset.value);
|
||||
const newValue = armorItem.system.marks.value >= armorValue ? armorValue - 1 : armorValue;
|
||||
await armorItem.update({ 'system.marks.value': newValue });
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens Compedium Browser
|
||||
*/
|
||||
static async #tempBrowser(_, target) {
|
||||
new ItemBrowser().render({ force: true });
|
||||
}
|
||||
|
||||
static async #refeshActions() {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: 'New Section',
|
||||
icon: 'fa-solid fa-campground'
|
||||
},
|
||||
content: await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs',
|
||||
{
|
||||
refreshables: DaggerheartMenu.defaultRefreshSelections()
|
||||
}
|
||||
),
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'tab', 'sidebar-tab', 'daggerheartMenu-sidebar']
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
static async #triggerRest(_, button) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.localize(`DAGGERHEART.APPLICATIONS.Downtime.${button.dataset.type}.title`),
|
||||
icon: button.dataset.type === 'shortRest' ? 'fa-solid fa-utensils' : 'fa-solid fa-bed'
|
||||
},
|
||||
content: 'This will trigger a dialog to players make their downtime moves, are you sure?',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style']
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
this.document.system.partyMembers.forEach(actor => {
|
||||
game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.DowntimeTrigger,
|
||||
data: {
|
||||
actorId: actor.uuid,
|
||||
downtimeType: button.dataset.type
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async downtimeMoveQuery({ actorId, downtimeType }) {
|
||||
const actor = await foundry.utils.fromUuid(actorId);
|
||||
if (!actor || !actor?.isOwner) reject();
|
||||
new game.system.api.applications.dialogs.Downtime(actor, downtimeType === 'shortRest').render({
|
||||
force: true
|
||||
});
|
||||
}
|
||||
|
||||
static async #tagTeamRoll() {
|
||||
new game.system.api.applications.dialogs.TagTeamDialog(this.document.system.partyMembers).render({
|
||||
force: true
|
||||
});
|
||||
}
|
||||
|
||||
static async #groupRoll(params) {
|
||||
new GroupRollDialog(this.document.system.partyMembers).render({ force: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for Consumable and Loot.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static #getItemContextOptions() {
|
||||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
/* Filter Tracking */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* The currently active search filter.
|
||||
* @type {foundry.applications.ux.SearchFilter}
|
||||
*/
|
||||
#search = {};
|
||||
|
||||
/**
|
||||
* The currently active search filter.
|
||||
* @type {FilterMenu}
|
||||
*/
|
||||
#menu = {};
|
||||
|
||||
/**
|
||||
* Tracks which item IDs are currently displayed, organized by filter type and section.
|
||||
* @type {{
|
||||
* inventory: {
|
||||
* search: Set<string>,
|
||||
* menu: Set<string>
|
||||
* },
|
||||
* loadout: {
|
||||
* search: Set<string>,
|
||||
* menu: Set<string>
|
||||
* },
|
||||
* }}
|
||||
*/
|
||||
#filteredItems = {
|
||||
inventory: {
|
||||
search: new Set(),
|
||||
menu: new Set()
|
||||
},
|
||||
loadout: {
|
||||
search: new Set(),
|
||||
menu: new Set()
|
||||
}
|
||||
};
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Search Inputs */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Create and initialize search filter instances for the inventory and loadout sections.
|
||||
*
|
||||
* Sets up two {@link foundry.applications.ux.SearchFilter} instances:
|
||||
* - One for the inventory, which filters items in the inventory grid.
|
||||
* - One for the loadout, which filters items in the loadout/card grid.
|
||||
* @private
|
||||
*/
|
||||
_createSearchFilter() {
|
||||
//Filters could be a application option if needed
|
||||
const filters = [
|
||||
{
|
||||
key: 'inventory',
|
||||
input: 'input[type="search"].search-inventory',
|
||||
content: '[data-application-part="inventory"] .items-section',
|
||||
callback: this._onSearchFilterInventory.bind(this)
|
||||
}
|
||||
];
|
||||
|
||||
for (const { key, input, content, callback } of filters) {
|
||||
const filter = new foundry.applications.ux.SearchFilter({
|
||||
inputSelector: input,
|
||||
contentSelector: content,
|
||||
callback
|
||||
});
|
||||
filter.bind(this.element);
|
||||
this.#search[key] = filter;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle invetory items search and filtering.
|
||||
* @param {KeyboardEvent} event The keyboard input event.
|
||||
* @param {string} query The input search string.
|
||||
* @param {RegExp} rgx The regular expression query that should be matched against.
|
||||
* @param {HTMLElement} html The container to filter items from.
|
||||
* @protected
|
||||
*/
|
||||
async _onSearchFilterInventory(_event, query, rgx, html) {
|
||||
this.#filteredItems.inventory.search.clear();
|
||||
|
||||
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||
const item = await getDocFromElement(li);
|
||||
const matchesSearch = !query || foundry.applications.ux.SearchFilter.testQuery(rgx, item.name);
|
||||
if (matchesSearch) this.#filteredItems.inventory.search.add(item.id);
|
||||
const { menu } = this.#filteredItems.inventory;
|
||||
li.hidden = !(menu.has(item.id) && matchesSearch);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Filter Menus */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
_createFilterMenus() {
|
||||
//Menus could be a application option if needed
|
||||
const menus = [
|
||||
{
|
||||
key: 'inventory',
|
||||
container: '[data-application-part="inventory"]',
|
||||
content: '.items-section',
|
||||
callback: this._onMenuFilterInventory.bind(this),
|
||||
target: '.filter-button',
|
||||
filters: FilterMenu.invetoryFilters
|
||||
}
|
||||
];
|
||||
|
||||
menus.forEach(m => {
|
||||
const container = this.element.querySelector(m.container);
|
||||
this.#menu[m.key] = new FilterMenu(container, m.target, m.filters, m.callback, {
|
||||
contentSelector: m.content
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback when filters change
|
||||
* @param {PointerEvent} event
|
||||
* @param {HTMLElement} html
|
||||
* @param {import('../ux/filter-menu.mjs').FilterItem[]} filters
|
||||
*/
|
||||
async _onMenuFilterInventory(_event, html, filters) {
|
||||
this.#filteredItems.inventory.menu.clear();
|
||||
|
||||
for (const li of html.querySelectorAll('.inventory-item')) {
|
||||
const item = await getDocFromElement(li);
|
||||
|
||||
const matchesMenu =
|
||||
filters.length === 0 || filters.some(f => foundry.applications.ux.SearchFilter.evaluateFilter(item, f));
|
||||
if (matchesMenu) this.#filteredItems.inventory.menu.add(item.id);
|
||||
|
||||
const { search } = this.#filteredItems.inventory;
|
||||
li.hidden = !(search.has(item.id) && matchesMenu);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
const item = event.currentTarget.closest('.inventory-item');
|
||||
|
||||
if (item) {
|
||||
const adversaryData = { type: 'Actor', uuid: item.dataset.itemUuid };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(adversaryData));
|
||||
event.dataTransfer.setDragImage(item, 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
// Prevent event bubbling to avoid duplicate handling
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const item = await foundry.utils.fromUuid(data.uuid);
|
||||
|
||||
if (item instanceof DhpActor) {
|
||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||
if (currentMembers.includes(data.uuid)) {
|
||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
|
||||
}
|
||||
|
||||
await this.document.update({ 'system.partyMembers': [...currentMembers, item.uuid] });
|
||||
} else if (item instanceof DHItem) {
|
||||
this.document.createEmbeddedDocuments('Item', [item.toObject()]);
|
||||
} else {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
|
||||
}
|
||||
}
|
||||
|
||||
static async #deletePartyMember(event, target) {
|
||||
const doc = await getDocFromElement(target.closest('.inventory-item'));
|
||||
|
||||
if (!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize('TYPES.Actor.adversary'),
|
||||
name: doc.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
|
||||
const newMemberdList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||
await this.document.update({ 'system.partyMembers': newMemberdList });
|
||||
}
|
||||
|
||||
static async #deleteItem(event, target) {
|
||||
const doc = await getDocFromElement(target.closest('.inventory-item'));
|
||||
if (!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize('TYPES.Actor.party'),
|
||||
name: doc.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
this.document.deleteEmbeddedDocuments('Item', [doc.id]);
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.isNPC = this.document.isNPC;
|
||||
context.useResourcePips = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
).useResourcePips;
|
||||
context.showAttribution = !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance)
|
||||
.hideAttribution;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,10 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
|
||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||
}
|
||||
|
||||
static #defaultRefreshSelections() {
|
||||
static defaultRefreshSelections() {
|
||||
return {
|
||||
session: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.session') },
|
||||
scene: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.scene') },
|
||||
|
|
@ -138,7 +138,7 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract
|
|||
types: `[${types}]`
|
||||
})
|
||||
);
|
||||
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
|
||||
this.refreshSelections = DaggerheartMenu.defaultRefreshSelections();
|
||||
|
||||
const cls = getDocumentClass('ChatMessage');
|
||||
const msg = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
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) {
|
||||
super(options);
|
||||
|
|
@ -35,7 +38,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
// }
|
||||
// },
|
||||
{
|
||||
name: 'Reroll Damage',
|
||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
condition: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
|
|
@ -65,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() {
|
||||
|
|
@ -164,6 +179,169 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
'system.roll': newRoll,
|
||||
'rolls': [parsedRoll]
|
||||
});
|
||||
|
||||
Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll });
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.Refresh,
|
||||
data: {
|
||||
refreshType: RefreshType.TagTeamRoll
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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 newMessageData = foundry.utils.deepClone(message.system);
|
||||
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
|
||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
||||
|
||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
||||
{ ...renderData, user: game.user }
|
||||
);
|
||||
const mess = game.messages.get(message._id);
|
||||
|
||||
await emitAsGM(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
mess.update.bind(mess),
|
||||
{
|
||||
...renderData,
|
||||
content: updatedContent
|
||||
},
|
||||
mess.uuid
|
||||
);
|
||||
}
|
||||
|
||||
async groupRollReroll(event, message) {
|
||||
const path = event.currentTarget.dataset.path;
|
||||
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
|
||||
const actor = game.actors.get(actorData._id);
|
||||
|
||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
||||
}
|
||||
|
||||
const traitLabel = game.i18n.localize(abilities[trait].label);
|
||||
|
||||
const config = {
|
||||
event: event,
|
||||
title: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
||||
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: traitLabel
|
||||
}),
|
||||
roll: {
|
||||
trait: trait,
|
||||
advantage: 0,
|
||||
modifiers: [{ label: traitLabel, value: actor.system.traits[trait].value }]
|
||||
},
|
||||
hasRoll: true,
|
||||
skips: {
|
||||
createMessage: true
|
||||
}
|
||||
};
|
||||
const result = await actor.diceRoll({
|
||||
...config,
|
||||
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${actor.name}`,
|
||||
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: traitLabel
|
||||
})
|
||||
});
|
||||
|
||||
const newMessageData = foundry.utils.deepClone(message.system);
|
||||
foundry.utils.setProperty(newMessageData, `${path}.result`, { ...result.roll, rerolled: true });
|
||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
||||
|
||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
||||
{ ...renderData, user: game.user }
|
||||
);
|
||||
const mess = game.messages.get(message._id);
|
||||
await emitAsGM(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
mess.update.bind(mess),
|
||||
{
|
||||
...renderData,
|
||||
content: updatedContent
|
||||
},
|
||||
mess.uuid
|
||||
);
|
||||
}
|
||||
|
||||
async groupRollSuccessEvent(event, message) {
|
||||
if (!game.user.isGM) {
|
||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.gmOnly'));
|
||||
}
|
||||
|
||||
const { path, success } = event.currentTarget.dataset;
|
||||
const { actor: actorData } = foundry.utils.getProperty(message.system, path);
|
||||
const actor = game.actors.get(actorData._id);
|
||||
|
||||
if (!actor.testUserPermission(game.user, 'OWNER')) {
|
||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
|
||||
}
|
||||
|
||||
const newMessageData = foundry.utils.deepClone(message.system);
|
||||
foundry.utils.setProperty(newMessageData, `${path}.manualSuccess`, Boolean(success));
|
||||
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };
|
||||
|
||||
const updatedContent = await foundry.applications.handlebars.renderTemplate(
|
||||
'systems/daggerheart/templates/ui/chat/groupRoll.hbs',
|
||||
{ ...renderData, user: game.user }
|
||||
);
|
||||
const mess = game.messages.get(message._id);
|
||||
await emitAsGM(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
mess.update.bind(mess),
|
||||
{
|
||||
...renderData,
|
||||
content: updatedContent
|
||||
},
|
||||
mess.uuid
|
||||
);
|
||||
}
|
||||
|
||||
async groupRollExpandSection(event) {
|
||||
event.target
|
||||
.closest('.group-roll-header-expand-section')
|
||||
.querySelectorAll('i')
|
||||
.forEach(element => {
|
||||
element.classList.toggle('fa-angle-up');
|
||||
element.classList.toggle('fa-angle-down');
|
||||
});
|
||||
event.target.closest('.group-roll-section').querySelector('.group-roll-content').classList.toggle('closed');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -93,16 +93,17 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
if (lite === true) {
|
||||
this.compendiumBrowserTypeKey = 'compendiumBrowserLite';
|
||||
}
|
||||
const userPresetPosition = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position) ;
|
||||
|
||||
const userPresetPosition = game.user.getFlag(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position
|
||||
);
|
||||
|
||||
options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position;
|
||||
|
||||
|
||||
if (!userPresetPosition) {
|
||||
const width = noFolder === true || lite === true ? 600 : 850;
|
||||
if (this.rendered)
|
||||
this.setPosition({ width });
|
||||
else
|
||||
options.position.width = width;
|
||||
if (this.rendered) this.setPosition({ width });
|
||||
else options.position.width = width;
|
||||
}
|
||||
|
||||
await super._preRender(context, options);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue