mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-06 12:54:16 +02:00
Merged with main
This commit is contained in:
commit
d78927d0c2
275 changed files with 5076 additions and 4132 deletions
|
|
@ -439,10 +439,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
||||
};
|
||||
|
||||
if (type === 'subclasses')
|
||||
if (type === 'subclasses') {
|
||||
const classItem = this.setup.class;
|
||||
const uuid = classItem?._stats.compendiumSource ?? classItem?.uuid;
|
||||
presets.filter = {
|
||||
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid }
|
||||
'system.linkedClass': { key: 'system.linkedClass', value: uuid }
|
||||
};
|
||||
}
|
||||
|
||||
if (equipment.includes(type))
|
||||
presets.filter = {
|
||||
|
|
@ -610,7 +613,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
[foundry.utils.randomID()]: {}
|
||||
};
|
||||
} else if (item.type === 'subclass' && event.target.closest('.subclass-card')) {
|
||||
if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||
const classSubclasses = await this.setup.class.system.fetchSubclasses();
|
||||
if (classSubclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
const excludedSourceData = this.browserSettings.excludedSources;
|
||||
const excludedPackData = this.browserSettings.excludedPacks;
|
||||
context.typePackCollections = game.packs.reduce((acc, pack) => {
|
||||
const { type, label, packageType, packageName: basePackageName, id } = pack.metadata;
|
||||
const { type, label, packageType, packageName: basePackageName, name, id } = pack.metadata;
|
||||
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
||||
|
||||
const isWorldPack = packageType === 'world';
|
||||
|
|
@ -68,13 +68,15 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
if (!acc[type].sources[packageName])
|
||||
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
|
||||
|
||||
const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type);
|
||||
const included =
|
||||
!excludedPackData[packageName] ||
|
||||
!excludedPackData[packageName][name]?.excludedDocumentTypes.includes(type);
|
||||
|
||||
acc[type].sources[packageName].packs.push({
|
||||
pack: id,
|
||||
name,
|
||||
type,
|
||||
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
|
||||
checked: checked
|
||||
checked: included
|
||||
});
|
||||
|
||||
return acc;
|
||||
|
|
@ -106,16 +108,16 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
toggleTypedPack(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const { type, pack } = event.target.dataset;
|
||||
const currentlyExcluded = this.browserSettings.excludedPacks[pack]
|
||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type)
|
||||
const { type, source, packName } = event.target.dataset;
|
||||
const currentlyExcluded = this.browserSettings.excludedPacks[source]?.[packName]
|
||||
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.includes(type)
|
||||
: false;
|
||||
|
||||
if (!this.browserSettings.excludedPacks[pack])
|
||||
this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] };
|
||||
this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded
|
||||
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type)
|
||||
: [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type];
|
||||
this.browserSettings.excludedPacks[source] ??= {};
|
||||
this.browserSettings.excludedPacks[source][packName] ??= { excludedDocumentTypes: [] };
|
||||
this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes = currentlyExcluded
|
||||
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.filter(x => x !== type)
|
||||
: [...(this.browserSettings.excludedPacks[source][packName]?.excludedDocumentTypes ?? []), type];
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ export { default as ImageSelectDialog } from './imageSelectDialog.mjs';
|
|||
export { default as ItemTransferDialog } from './itemTransfer.mjs';
|
||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||
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 TagTeamDialog } from './tagTeamDialog.mjs';
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ export default class ActionSelectionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
static async #onChooseAction(event, button) {
|
||||
const { actionId } = button.dataset;
|
||||
this.action = this.item.system.actionsList.find(a => a._id === actionId);
|
||||
Object.defineProperty(this.event, 'shiftKey', {
|
||||
this.#action = this.item.system.actionsList.find(a => a._id === actionId);
|
||||
Object.defineProperty(this.#event, 'shiftKey', {
|
||||
get() {
|
||||
return event.shiftKey;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -175,14 +175,14 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.disadvantage = advantage === -1;
|
||||
|
||||
this.config.roll.advantage = this.config.roll.advantage === advantage ? 0 : advantage;
|
||||
if (this.config.roll.advantage === 0) return this.render();
|
||||
|
||||
if (this.config.roll.advantage === 1 && this.config.data.rules.roll.advantageFaces) {
|
||||
const faces = Number.parseInt(this.config.data.rules.roll.advantageFaces);
|
||||
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||
} else if (this.config.roll.advantage === -1 && this.config.data.rules.roll.disadvantageFaces) {
|
||||
const faces = Number.parseInt(this.config.data.rules.roll.disadvantageFaces);
|
||||
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||
}
|
||||
const defaultFaces =
|
||||
this.config.roll.advantage === 1
|
||||
? this.config.data.rules.roll.defaultAdvantageDice
|
||||
: this.config.data.rules.roll.defaultDisadvantageDice;
|
||||
const faces = Number.parseInt(defaultFaces);
|
||||
this.roll.advantageFaces = Number.isNaN(faces) ? this.roll.advantageFaces : faces;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,9 +22,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
);
|
||||
|
||||
const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
|
||||
const armor = orderedArmorSources.reduce((acc, { document }) => {
|
||||
const armor = orderedArmorSources.reduce((acc, { name, document }) => {
|
||||
const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
|
||||
acc.push({
|
||||
name,
|
||||
effect: document,
|
||||
marks: [...Array(max).keys()].reduce((acc, _, index) => {
|
||||
const spent = index < current;
|
||||
|
|
@ -152,14 +153,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
const armorSources = [];
|
||||
for (const source of this.marks.armor) {
|
||||
const parent = source.effect.origin
|
||||
? await foundry.utils.fromUuid(source.effect.origin)
|
||||
: source.effect.parent;
|
||||
|
||||
const useEffectName = parent.type === 'armor' || parent instanceof Actor;
|
||||
const label = useEffectName ? source.effect.name : parent.name;
|
||||
armorSources.push({
|
||||
label: label,
|
||||
label: source.name,
|
||||
uuid: source.effect.uuid,
|
||||
marks: source.marks
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||
const maxHope = this.actor.system.resources.hope.max + this.actor.system.scars;
|
||||
const newScarAmount = this.actor.system.scars + 1;
|
||||
await this.actor.update({
|
||||
system: {
|
||||
|
|
@ -64,7 +65,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
});
|
||||
|
||||
if (newScarAmount >= this.actor.system.resources.hope.max) {
|
||||
if (newScarAmount >= maxHope) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,7 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
const resetValue = increasing
|
||||
? 0
|
||||
: feature.system.resource.max
|
||||
? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total
|
||||
? new Roll(
|
||||
Roll.replaceFormulaData(feature.system.resource.max, this.actor.getRollData())
|
||||
).evaluateSync().total
|
||||
: 0;
|
||||
|
||||
await feature.update({ 'system.resource.value': resetValue });
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(party) {
|
||||
super();
|
||||
super({ id: `GroupRollDialog-${party.id}` });
|
||||
|
||||
this.party = party;
|
||||
this.partyMembers = party.system.partyMembers
|
||||
|
|
@ -35,19 +35,18 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'GroupRollDialog',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
|
||||
position: { width: 550, height: 'auto' },
|
||||
position: { width: 390, height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-users'
|
||||
},
|
||||
actions: {
|
||||
toggleSelectMember: this.#toggleSelectMember,
|
||||
startGroupRoll: this.#startGroupRoll,
|
||||
makeRoll: this.#makeRoll,
|
||||
removeRoll: this.#removeRoll,
|
||||
rerollDice: this.#rerollDice,
|
||||
makeLeaderRoll: this.#makeLeaderRoll,
|
||||
removeLeaderRoll: this.#removeLeaderRoll,
|
||||
rerollLeaderDice: this.#rerollLeaderDice,
|
||||
markSuccessfull: this.#markSuccessfull,
|
||||
markSuccessful: this.#markSuccessful,
|
||||
cancelRoll: this.#onCancelRoll,
|
||||
finishRoll: this.#finishRoll
|
||||
},
|
||||
|
|
@ -59,17 +58,21 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
id: 'initialization',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/initialization.hbs'
|
||||
},
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/main.hbs'
|
||||
},
|
||||
leader: {
|
||||
id: 'leader',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/leader.hbs'
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/member.hbs'
|
||||
},
|
||||
groupRoll: {
|
||||
id: 'groupRoll',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRoll.hbs'
|
||||
result: {
|
||||
id: 'result',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/result.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/footer.hbs'
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -89,51 +92,31 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
|
||||
_configureRenderParts(options) {
|
||||
const { initialization, leader, groupRoll, footer } = super._configureRenderParts(options);
|
||||
const augmentedParts = { initialization };
|
||||
const parts = super._configureRenderParts(options);
|
||||
for (const memberKey of Object.keys(this.party.system.groupRoll.aidingCharacters)) {
|
||||
augmentedParts[memberKey] = {
|
||||
parts[memberKey] = {
|
||||
id: memberKey,
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/groupRollMember.hbs'
|
||||
template: 'systems/daggerheart/templates/dialogs/groupRollDialog/parts/member.hbs'
|
||||
};
|
||||
}
|
||||
|
||||
augmentedParts.leader = leader;
|
||||
augmentedParts.groupRoll = groupRoll;
|
||||
augmentedParts.footer = footer;
|
||||
|
||||
return augmentedParts;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
if (this.element.querySelector('.team-container')) return;
|
||||
|
||||
if (this.tabGroups.application !== this.constructor.PARTS.initialization.id) {
|
||||
const initializationPart = this.element.querySelector('.initialization-container');
|
||||
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
|
||||
initializationPart.insertAdjacentHTML(
|
||||
'afterend',
|
||||
`<div class="section-title">${game.i18n.localize('DAGGERHEART.APPLICATIONS.GroupRollSelect.aidingCharacters')}</div>`
|
||||
);
|
||||
|
||||
const teamContainer = this.element.querySelector('.team-container');
|
||||
for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
|
||||
teamContainer.appendChild(memberContainer);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
|
||||
context.isGM = game.user.isGM;
|
||||
context.isEditable = this.getIsEditable();
|
||||
context.isEditable =
|
||||
game.user.isGM ||
|
||||
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);
|
||||
});
|
||||
context.fields = this.party.system.schema.fields.groupRoll.fields;
|
||||
context.data = this.party.system.groupRoll;
|
||||
context.traitOptions = CONFIG.DH.ACTOR.abilities;
|
||||
context.members = {};
|
||||
context.aidKeys = Object.keys(this.party.system.groupRoll.aidingCharacters);
|
||||
context.allHaveRolled = Object.keys(context.data.participants).every(key => {
|
||||
const data = context.data.participants[key];
|
||||
return Boolean(data.rollData);
|
||||
|
|
@ -145,6 +128,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
async _preparePartContext(partId, context, options) {
|
||||
const partContext = await super._preparePartContext(partId, context, options);
|
||||
partContext.partId = partId;
|
||||
partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
|
||||
|
||||
switch (partId) {
|
||||
case 'initialization':
|
||||
|
|
@ -162,19 +146,14 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
partContext.canStartGroupRoll = selectedMembers.length > 1 && this.leader?.memberId;
|
||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||
break;
|
||||
case 'leader':
|
||||
partContext.leader = this.getRollCharacterData(this.party.system.groupRoll.leader);
|
||||
break;
|
||||
case 'groupRoll':
|
||||
case 'result':
|
||||
const leader = this.party.system.groupRoll.leader;
|
||||
partContext.hasRolled =
|
||||
leader?.rollData ||
|
||||
Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some(
|
||||
x => x.successfull !== null
|
||||
);
|
||||
Object.values(this.party.system.groupRoll?.aidingCharacters ?? {}).some(x => x.successful !== null);
|
||||
const { modifierTotal, modifiers } = Object.values(this.party.system.groupRoll.aidingCharacters).reduce(
|
||||
(acc, curr) => {
|
||||
const modifier = curr.successfull === true ? 1 : curr.successfull === false ? -1 : null;
|
||||
const modifier = curr.successful === true ? 1 : curr.successful === false ? -1 : null;
|
||||
if (modifier) {
|
||||
acc.modifierTotal += modifier;
|
||||
acc.modifiers.push(modifier);
|
||||
|
|
@ -200,7 +179,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
case 'footer':
|
||||
partContext.canFinishRoll =
|
||||
Boolean(this.party.system.groupRoll.leader?.rollData) &&
|
||||
Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successfull !== null);
|
||||
Object.values(this.party.system.groupRoll.aidingCharacters).every(x => x.successful !== null);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -216,20 +195,42 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
if (!data) return {};
|
||||
|
||||
const actor = game.actors.get(data.id);
|
||||
const isLeader = data === this.party.system.groupRoll.leader;
|
||||
|
||||
const roll = data.roll;
|
||||
const withTypeSuffix = !roll ? null : roll.isCritical ? 'criticalShort' : roll.withHope ? 'hope' : 'fear';
|
||||
const thing = withTypeSuffix ? _loc(`DAGGERHEART.GENERAL.${withTypeSuffix}`) : null;
|
||||
|
||||
return {
|
||||
...data,
|
||||
type: isLeader ? 'leader' : 'aid',
|
||||
basePath: isLeader ? 'system.groupRoll.leader' : `system.groupRoll.aidingCharacters.${data.id}`,
|
||||
rollChoiceLabel: _loc(CONFIG.DH.ACTOR.abilities[data.rollChoice]?.label),
|
||||
roll: data.roll,
|
||||
isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
isEditable: actor?.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: partId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
hasRolled: Boolean(data.rollData)
|
||||
hasRolled: Boolean(data.rollData),
|
||||
modifier: data.successful ? 1 : data.successful === false ? -1 : 0,
|
||||
withLabelShort: thing ? _loc('DAGGERHEART.GENERAL.withThing', { thing }) : null
|
||||
};
|
||||
}
|
||||
|
||||
#getCharacterDataById(id) {
|
||||
if (!id) return null;
|
||||
|
||||
const groupRoll = this.party.system.groupRoll;
|
||||
if (id === 'leader' || id === groupRoll.leader?.id) {
|
||||
return { data: groupRoll.leader, basePath: 'system.groupRoll.leader' };
|
||||
} else if (id in groupRoll.aidingCharacters) {
|
||||
return { data: groupRoll.aidingCharacters[id], basePath: `system.groupRoll.aidingCharacters.${id}` };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static async updateData(event, _, formData) {
|
||||
const partyData = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.updatePartyData(partyData, this.getUpdatingParts(event.target));
|
||||
}
|
||||
|
||||
|
|
@ -246,7 +247,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
});
|
||||
};
|
||||
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
gmUpdate,
|
||||
update,
|
||||
|
|
@ -256,26 +257,19 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
|
||||
getUpdatingParts(target) {
|
||||
const { initialization, leader, groupRoll, footer } = this.constructor.PARTS;
|
||||
const { initialization, leader, result, footer } = this.constructor.PARTS;
|
||||
const isInitialization = this.tabGroups.application === initialization.id;
|
||||
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
|
||||
const updatingLeader = target.closest('.main-character-outer-container');
|
||||
const updatingMember = target.closest('.member-roll-container.aid')?.dataset?.memberKey;
|
||||
const updatingLeader = target.closest('.member-roll-container.leader');
|
||||
|
||||
return [
|
||||
...(isInitialization ? [initialization.id] : []),
|
||||
...(updatingMember ? [updatingMember] : []),
|
||||
...(updatingLeader ? [leader.id] : []),
|
||||
...(!isInitialization ? [groupRoll.id, footer.id] : [])
|
||||
...(!isInitialization ? [result.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;
|
||||
|
||||
|
|
@ -304,6 +298,9 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
static #toggleSelectMember(_, button) {
|
||||
const member = this.partyMembers.find(x => x.id === button.dataset.id);
|
||||
member.selected = !member.selected;
|
||||
if (this.leader?.memberId === member.id) {
|
||||
this.leader = null;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
|
@ -343,11 +340,14 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
}
|
||||
//#endregion
|
||||
|
||||
async makeRoll(button, characterData, path) {
|
||||
const actor = game.actors.find(x => x.id === characterData.id);
|
||||
/** @this GroupRollDialog */
|
||||
static async #makeRoll(_event, button) {
|
||||
const member = button.closest('[data-member-key]').dataset.memberKey;
|
||||
const { data, basePath } = this.#getCharacterDataById(member);
|
||||
const actor = game.actors.find(x => x.id === data.id);
|
||||
if (!actor) return;
|
||||
|
||||
const result = await actor.rollTrait(characterData.rollChoice, {
|
||||
const result = await actor.rollTrait(data.rollChoice, {
|
||||
skips: {
|
||||
createMessage: true,
|
||||
resources: true,
|
||||
|
|
@ -356,53 +356,38 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
});
|
||||
|
||||
if (!result) return;
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const rollData = result.messageRoll.toJSON();
|
||||
delete rollData.options.messageRoll;
|
||||
this.updatePartyData(
|
||||
{
|
||||
[path]: rollData
|
||||
[basePath]: { rollData, successful: null }
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
static async #makeRoll(_event, button) {
|
||||
const { member } = button.dataset;
|
||||
const character = this.party.system.groupRoll.aidingCharacters[member];
|
||||
this.makeRoll(button, character, `system.groupRoll.aidingCharacters.${member}.rollData`);
|
||||
}
|
||||
|
||||
static async #makeLeaderRoll(_event, button) {
|
||||
const character = this.party.system.groupRoll.leader;
|
||||
this.makeRoll(button, character, 'system.groupRoll.leader.rollData');
|
||||
}
|
||||
|
||||
async removeRoll(button, path) {
|
||||
/** @this GroupRollDialog */
|
||||
static async #removeRoll(_event, button) {
|
||||
const member = button.closest('[data-member-key]').dataset.memberKey;
|
||||
const { basePath } = this.#getCharacterDataById(member);
|
||||
this.updatePartyData(
|
||||
{
|
||||
[path]: {
|
||||
[basePath]: {
|
||||
rollData: null,
|
||||
rollChoice: null,
|
||||
selected: false,
|
||||
successfull: null
|
||||
successful: null
|
||||
}
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
static async #removeRoll(_event, button) {
|
||||
this.removeRoll(button, `system.groupRoll.aidingCharacters.${button.dataset.member}`);
|
||||
}
|
||||
|
||||
static async #removeLeaderRoll(_event, button) {
|
||||
this.removeRoll(button, 'system.groupRoll.leader');
|
||||
}
|
||||
|
||||
async rerollDice(button, data, path) {
|
||||
/** @this GroupRollDialog */
|
||||
static async #rerollDice(_, button) {
|
||||
const { diceType } = button.dataset;
|
||||
const { data, basePath } = this.#getCharacterDataById(button.dataset.member);
|
||||
|
||||
const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2;
|
||||
const newRoll = game.system.api.dice.DualityRoll.fromData(data.rollData);
|
||||
|
|
@ -416,31 +401,19 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
const rollData = newRoll.toJSON();
|
||||
this.updatePartyData(
|
||||
{
|
||||
[path]: rollData
|
||||
[`${basePath}.rollData`]: rollData
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
);
|
||||
}
|
||||
|
||||
static async #rerollDice(_, button) {
|
||||
const { member } = button.dataset;
|
||||
this.rerollDice(
|
||||
button,
|
||||
this.party.system.groupRoll.aidingCharacters[member],
|
||||
`system.groupRoll.aidingCharacters.${member}.rollData`
|
||||
);
|
||||
}
|
||||
|
||||
static async #rerollLeaderDice(_, button) {
|
||||
this.rerollDice(button, this.party.system.groupRoll.leader, `system.groupRoll.leader.rollData`);
|
||||
}
|
||||
|
||||
static #markSuccessfull(_event, button) {
|
||||
const previousValue = this.party.system.groupRoll.aidingCharacters[button.dataset.member].successfull;
|
||||
const newValue = Boolean(button.dataset.successfull === 'true');
|
||||
static #markSuccessful(_event, button) {
|
||||
const memberKey = button.closest('[data-member-key]').dataset.memberKey;
|
||||
const previousValue = this.party.system.groupRoll.aidingCharacters[memberKey].successful;
|
||||
const newValue = Boolean(button.dataset.success === 'true');
|
||||
this.updatePartyData(
|
||||
{
|
||||
[`system.groupRoll.aidingCharacters.${button.dataset.member}.successfull`]:
|
||||
[`system.groupRoll.aidingCharacters.${memberKey}.successful`]:
|
||||
previousValue === newValue ? null : newValue
|
||||
},
|
||||
this.getUpdatingParts(button)
|
||||
|
|
@ -484,7 +457,7 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
|
|||
static async #finishRoll() {
|
||||
const totalRoll = this.party.system.groupRoll.leader.roll;
|
||||
for (const character of Object.values(this.party.system.groupRoll.aidingCharacters)) {
|
||||
totalRoll.terms.push(new foundry.dice.terms.OperatorTerm({ operator: character.successfull ? '+' : '-' }));
|
||||
totalRoll.terms.push(new foundry.dice.terms.OperatorTerm({ operator: character.successful ? '+' : '-' }));
|
||||
totalRoll.terms.push(new foundry.dice.terms.NumericTerm({ number: 1 }));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,280 +0,0 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDamageDialog.#toggleResult,
|
||||
selectRoll: RerollDamageDialog.#selectRoll,
|
||||
doReroll: RerollDamageDialog.#doReroll,
|
||||
save: RerollDamageDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDialog.#toggleResult,
|
||||
selectRoll: RerollDialog.#selectRoll,
|
||||
doReroll: RerollDialog.#doReroll,
|
||||
save: RerollDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { itemAbleRollParse } from '../../helpers/utils.mjs';
|
||||
import { itemAbleRollParse, triggerChatRollFx } from '../../helpers/utils.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli
|
|||
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
|
||||
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
|
||||
const roll = await new Roll(diceFormula).evaluate();
|
||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||
await triggerChatRollFx([roll]);
|
||||
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));
|
||||
this.resetUsed = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { MemberData } from '../../data/tagTeamData.mjs';
|
||||
import { getCritDamageBonus } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import Party from '../sheets/actors/party.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(party) {
|
||||
super();
|
||||
super({ id: `TagTeamDialog-${party.id}` });
|
||||
|
||||
this.party = party;
|
||||
this.partyMembers = party.system.partyMembers
|
||||
|
|
@ -36,9 +36,11 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'TagTeamDialog',
|
||||
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
|
||||
position: { width: 550, height: 'auto' },
|
||||
window: {
|
||||
icon: 'fa-solid fa-user-group'
|
||||
},
|
||||
actions: {
|
||||
toggleSelectMember: TagTeamDialog.#toggleSelectMember,
|
||||
startTagTeamRoll: TagTeamDialog.#startTagTeamRoll,
|
||||
|
|
@ -60,13 +62,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
id: 'initialization',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs'
|
||||
},
|
||||
tagTeamRoll: {
|
||||
id: 'tagTeamRoll',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs'
|
||||
},
|
||||
rollSelection: {
|
||||
id: 'rollSelection',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/rollSelection.hbs'
|
||||
},
|
||||
tagTeamRoll: {
|
||||
id: 'tagTeamRoll',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs'
|
||||
result: {
|
||||
id: 'result',
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/result.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -97,41 +103,25 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
_configureRenderParts(options) {
|
||||
const { initialization, rollSelection, tagTeamRoll } = super._configureRenderParts(options);
|
||||
const augmentedParts = { initialization };
|
||||
const parts = super._configureRenderParts(options);
|
||||
for (const memberKey of Object.keys(this.party.system.tagTeam.members)) {
|
||||
augmentedParts[memberKey] = {
|
||||
parts[memberKey] = {
|
||||
id: memberKey,
|
||||
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamMember.hbs'
|
||||
};
|
||||
}
|
||||
augmentedParts.rollSelection = rollSelection;
|
||||
augmentedParts.tagTeamRoll = tagTeamRoll;
|
||||
|
||||
return augmentedParts;
|
||||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
||||
// if (this.element.querySelector('.roll-selection')) {
|
||||
// for (const element of this.element.querySelectorAll('.team-member-container')) {
|
||||
// element.classList.add('select-padding');
|
||||
// }
|
||||
// }
|
||||
|
||||
if (this.element.querySelector('.team-container')) return;
|
||||
const initializationPart = this.element.querySelector('.initialization-container');
|
||||
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
|
||||
const teamContainer = this.element.querySelector('.team-container');
|
||||
for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
|
||||
teamContainer.appendChild(memberContainer);
|
||||
return parts;
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.isEditable = this.getIsEditable();
|
||||
context.isEditable =
|
||||
game.user.isGM ||
|
||||
this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.tagTeam.members[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
context.fields = this.party.system.schema.fields.tagTeam.fields;
|
||||
context.data = this.party.system.tagTeam;
|
||||
context.rollTypes = CONFIG.DH.GENERAL.tagTeamRollTypes;
|
||||
|
|
@ -167,6 +157,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
partContext.initiatorDisabled = !selectedMembers.length;
|
||||
partContext.openForAllPlayers = this.openForAllPlayers;
|
||||
|
||||
break;
|
||||
case 'tagTeamRoll':
|
||||
partContext.memberKeys = Object.keys(this.party.system.tagTeam.members);
|
||||
break;
|
||||
case 'rollSelection':
|
||||
partContext.members = Object.keys(this.party.system.tagTeam.members).reduce((acc, key) => {
|
||||
|
|
@ -175,7 +168,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
return acc;
|
||||
}, {});
|
||||
break;
|
||||
case 'tagTeamRoll':
|
||||
case 'result':
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll
|
||||
? undefined
|
||||
|
|
@ -191,59 +184,58 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
if (Object.keys(this.party.system.tagTeam.members).includes(partId)) {
|
||||
const data = this.party.system.tagTeam.members[partId];
|
||||
const actor = game.actors.get(partId);
|
||||
|
||||
const rollOptions = [];
|
||||
const damageRollOptions = [];
|
||||
for (const item of actor.items) {
|
||||
if (item.system.metadata.hasActions) {
|
||||
const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])];
|
||||
for (const action of actions) {
|
||||
if (action.hasRoll) {
|
||||
const actionItem = {
|
||||
value: action.uuid,
|
||||
label: action.name,
|
||||
group: item.name,
|
||||
baseAction: action.baseAction
|
||||
};
|
||||
|
||||
if (action.hasDamage) damageRollOptions.push(actionItem);
|
||||
else rollOptions.push(actionItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll
|
||||
? undefined
|
||||
: (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
|
||||
const damage = data.rollData?.options?.damage;
|
||||
partContext.hasDamage |= Boolean(damage);
|
||||
const critHitPointsDamage = await this.getCriticalDamage(damage);
|
||||
|
||||
partContext.members[partId] = {
|
||||
...data,
|
||||
roll: data.roll,
|
||||
isEditable: actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER),
|
||||
key: partId,
|
||||
readyToRoll: Boolean(data.rollChoice),
|
||||
hasRolled: Boolean(data.rollData),
|
||||
rollOptions,
|
||||
damageRollOptions,
|
||||
damage: damage,
|
||||
critDamage: critHitPointsDamage,
|
||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
||||
};
|
||||
const data = await this.#prepareMemberContext(partId);
|
||||
partContext.hasDamage |= Boolean(data?.damage);
|
||||
partContext.members[partId] = data;
|
||||
}
|
||||
|
||||
return partContext;
|
||||
}
|
||||
|
||||
async #prepareMemberContext(partId) {
|
||||
const data = this.party.system.tagTeam.members[partId] ?? {};
|
||||
const actor = game.actors.get(partId);
|
||||
if (!actor) console.error(`Failed to get actor ${partId}`);
|
||||
|
||||
const rollOptions = [];
|
||||
const damageRollOptions = [];
|
||||
for (const item of actor?.items ?? []) {
|
||||
if (!item.system.metadata.hasActions) continue;
|
||||
const actions = [...item.system.actions, ...(item.system.attack ? [item.system.attack] : [])];
|
||||
for (const action of actions) {
|
||||
if (action.hasRoll) {
|
||||
const collection = action.hasDamage ? damageRollOptions : rollOptions;
|
||||
collection.push({
|
||||
value: action.uuid,
|
||||
label: action.name,
|
||||
group: item.name,
|
||||
baseAction: action.baseAction
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
|
||||
const critSelected = !selectedRoll ? undefined : (selectedRoll?.rollData?.options?.roll?.isCritical ?? false);
|
||||
const damage = data.rollData?.options?.damage;
|
||||
|
||||
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),
|
||||
rollOptions,
|
||||
damageRollOptions,
|
||||
damage: damage,
|
||||
critDamage: await this.getCriticalDamage(damage),
|
||||
useCritDamage: critSelected || (critSelected === undefined && data.rollData?.options?.roll?.isCritical)
|
||||
};
|
||||
}
|
||||
|
||||
getUpdatingParts(target) {
|
||||
const { initialization, rollSelection, tagTeamRoll } = this.constructor.PARTS;
|
||||
const { initialization, rollSelection, result } = this.constructor.PARTS;
|
||||
const isInitialization = this.tabGroups.application === initialization.id;
|
||||
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
|
||||
|
||||
|
|
@ -251,7 +243,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
...(isInitialization ? [initialization.id] : []),
|
||||
...(updatingMember ? [updatingMember] : []),
|
||||
...(!isInitialization ? [rollSelection.id] : []),
|
||||
...(!isInitialization ? [tagTeamRoll.id] : [])
|
||||
...(!isInitialization ? [result.id] : [])
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -274,7 +266,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
});
|
||||
};
|
||||
|
||||
await emitAsGM(
|
||||
await emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
gmUpdate,
|
||||
update,
|
||||
|
|
@ -285,13 +277,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
);
|
||||
}
|
||||
|
||||
getIsEditable() {
|
||||
return this.party.system.partyMembers.some(actor => {
|
||||
const selected = Boolean(this.party.system.tagTeam.members[actor.id]);
|
||||
return selected && actor.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER);
|
||||
});
|
||||
}
|
||||
|
||||
tagTeamRefresh = ({ refreshType, action, parts }) => {
|
||||
if (refreshType !== RefreshType.TagTeamRoll) return;
|
||||
|
||||
|
|
@ -446,8 +431,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
if (!result) return;
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
const rollData = result.messageRoll.toJSON();
|
||||
delete rollData.options.messageRoll;
|
||||
this.updatePartyData(
|
||||
|
|
@ -663,42 +646,50 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
async getJoinedRoll({ overrideIsCritical, displayVersion } = {}) {
|
||||
const memberValues = Object.values(this.party.system.tagTeam.members);
|
||||
const selectedRoll = memberValues.find(x => x.selected);
|
||||
let baseMainRoll = selectedRoll ?? memberValues[0];
|
||||
let baseSecondaryRoll = selectedRoll
|
||||
? memberValues.find(x => !x.selected)
|
||||
: memberValues.length > 1
|
||||
? memberValues[1]
|
||||
: null;
|
||||
try {
|
||||
const memberValues = Object.values(this.party.system.tagTeam.members);
|
||||
const selectedRoll = memberValues.find(x => x.selected);
|
||||
const baseMainRoll = selectedRoll ?? memberValues[0];
|
||||
const baseSecondaryRoll = selectedRoll
|
||||
? memberValues.find(x => !x.selected)
|
||||
: memberValues.length > 1
|
||||
? memberValues[1]
|
||||
: null;
|
||||
|
||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||
if (!baseMainRoll?.rollData || !baseSecondaryRoll) return null;
|
||||
|
||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
||||
const systemData = mainRoll.rollData.options;
|
||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
||||
const mainRoll = new MemberData(baseMainRoll.toObject());
|
||||
const secondaryRollData = new MemberData(baseSecondaryRoll.toObject()).rollData;
|
||||
const systemData = mainRoll.rollData.options;
|
||||
const isCritical = overrideIsCritical ?? systemData.roll.isCritical;
|
||||
if (isCritical) systemData.damage = await this.getCriticalDamage(systemData.damage);
|
||||
|
||||
if (secondaryRollData?.options.hasDamage) {
|
||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
||||
: secondaryRollData.options.damage;
|
||||
if (systemData.damage) {
|
||||
for (const key in secondaryDamage) {
|
||||
const damage = secondaryDamage[key];
|
||||
systemData.damage[key].formula = [systemData.damage[key].formula, damage.formula]
|
||||
.filter(x => x)
|
||||
.join(' + ');
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts.push(...damage.parts);
|
||||
if (secondaryRollData?.options.hasDamage) {
|
||||
const secondaryDamage = (displayVersion ? overrideIsCritical : isCritical)
|
||||
? await this.getCriticalDamage(secondaryRollData.options.damage)
|
||||
: secondaryRollData.options.damage;
|
||||
if (systemData.damage) {
|
||||
for (const [key, damage] of Object.entries(secondaryDamage ?? {})) {
|
||||
if (key in systemData.damage) {
|
||||
systemData.damage[key].formula = [systemData.damage[key]?.formula, damage.formula]
|
||||
.filter(x => x)
|
||||
.join(' + ');
|
||||
systemData.damage[key].total += damage.total;
|
||||
systemData.damage[key].parts.push(...damage.parts);
|
||||
} else {
|
||||
systemData.damage[key] = damage;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
systemData.damage = secondaryDamage;
|
||||
}
|
||||
} else {
|
||||
systemData.damage = secondaryDamage;
|
||||
}
|
||||
}
|
||||
|
||||
return mainRoll;
|
||||
return mainRoll;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static async #onCancelRoll(_event, _button, options = { confirm: true }) {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
const animationDuration = 500;
|
||||
const scene = game.scenes.get(game.user.viewedScene);
|
||||
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
|
||||
const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id));
|
||||
const activeTokens = actors.flatMap(member =>
|
||||
member.getDependentTokens({ scenes: scene }).filter(x => x._id && !x._destroyed)
|
||||
);
|
||||
const { x: actorX, y: actorY } = this.document;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
if (multiclasses?.[0]) {
|
||||
const data = multiclasses[0];
|
||||
const multiclass = data.data.length > 0 ? await foundry.utils.fromUuid(data.data[0]) : {};
|
||||
const subclasses = (await multiclass?.system?.fetchSubclasses()) ?? [];
|
||||
|
||||
context.multiclass = {
|
||||
...data,
|
||||
|
|
@ -173,13 +174,12 @@ export default class DhCharacterLevelUp extends LevelUpBase {
|
|||
alreadySelected
|
||||
};
|
||||
}) ?? [],
|
||||
subclasses:
|
||||
multiclass?.system?.subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})) ?? [],
|
||||
subclasses: subclasses.map(subclass => ({
|
||||
...subclass,
|
||||
uuid: subclass.uuid,
|
||||
selected: data.secondaryData.subclass === subclass.uuid,
|
||||
disabled: data.secondaryData.subclass && data.secondaryData.subclass !== subclass.uuid
|
||||
})),
|
||||
compendium: 'classes',
|
||||
limit: 1
|
||||
};
|
||||
|
|
|
|||
|
|
@ -358,14 +358,14 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
|
|||
const experienceIncreaseTagify = htmlElement.querySelector('.levelup-experience-increases');
|
||||
if (experienceIncreaseTagify) {
|
||||
const allExperiences = {
|
||||
...this.actor.system.experiences,
|
||||
...Object.values(this.levelup.levels).reduce((acc, level) => {
|
||||
for (const key of Object.keys(level.achievements.experiences)) {
|
||||
acc[key] = level.achievements.experiences[key];
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
}, {}),
|
||||
...this.actor.system.experiences
|
||||
};
|
||||
tagifyElement(
|
||||
experienceIncreaseTagify,
|
||||
|
|
|
|||
|
|
@ -120,12 +120,6 @@ export default class DhSceneConfigSettings extends foundry.applications.sheets.S
|
|||
foundry.utils.fromUuidSync(x)
|
||||
);
|
||||
|
||||
for (const key of Object.keys(this.document._source.flags.daggerheart?.sceneEnvironments ?? {})) {
|
||||
if (!submitData.flags.daggerheart.sceneEnvironments[key]) {
|
||||
submitData.flags.daggerheart.sceneEnvironments[key] = _del;
|
||||
}
|
||||
}
|
||||
|
||||
super._processSubmitData(event, form, submitData, options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { DhHomebrew } from '../../data/settings/_module.mjs';
|
||||
import { Resource } from '../../data/settings/Homebrew.mjs';
|
||||
import { slugify } from '../../helpers/utils.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -112,7 +111,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
|
||||
switch (partId) {
|
||||
case 'domains':
|
||||
const selectedDomain = this.selected.domain ? this.settings.domains[this.selected.domain] : null;
|
||||
const selectedDomain = this.settings.domains[this.selected.domain] ?? null;
|
||||
const enrichedDescription = selectedDomain
|
||||
? await foundry.applications.ux.TextEditor.implementation.enrichHTML(selectedDomain.description)
|
||||
: null;
|
||||
|
|
@ -403,12 +402,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
const domainName = button.form.elements.domainName.value;
|
||||
if (!domainName) return;
|
||||
|
||||
const newSlug = slugify(domainName);
|
||||
const newSlug = domainName.slugify();
|
||||
const existingDomains = [
|
||||
...Object.values(this.settings.domains),
|
||||
...Object.values(CONFIG.DH.DOMAIN.domains)
|
||||
];
|
||||
if (existingDomains.find(x => slugify(game.i18n.localize(x.label)) === newSlug)) {
|
||||
if (existingDomains.find(x => x.id === newSlug)) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain'));
|
||||
return;
|
||||
}
|
||||
|
|
@ -529,7 +528,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
|
|||
const identifier = button.form.elements.identifier.value;
|
||||
if (!identifier) return;
|
||||
|
||||
const sluggedIdentifier = slugify(identifier);
|
||||
const sluggedIdentifier = identifier.slugify();
|
||||
|
||||
await this.settings.updateSource({
|
||||
[`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export { default as ActionConfig } from './action-config.mjs';
|
|||
export { default as ActionSettingsConfig } from './action-settings-config.mjs';
|
||||
export { default as CharacterSettings } from './character-settings.mjs';
|
||||
export { default as AdversarySettings } from './adversary-settings.mjs';
|
||||
export { default as NPCSettings } from './npc-settings.mjs';
|
||||
export { default as CompanionSettings } from './companion-settings.mjs';
|
||||
export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
|
||||
export { default as EnvironmentSettings } from './environment-settings.mjs';
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
context.openSection = this.openSection;
|
||||
context.tabs = this._getTabs(this.constructor.TABS);
|
||||
context.config = CONFIG.DH;
|
||||
if (this.action.hasDamage) {
|
||||
if (this.action.damage) {
|
||||
context.allDamageTypesUsed = !getUnusedDamageTypes(this.action.damage.parts).length;
|
||||
|
||||
if (this.action.damage.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||
|
|
@ -204,7 +204,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata?.isQuantifiable) {
|
||||
if (this.action.parent.metadata?.isInventoryItem) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
|
|
@ -302,7 +302,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
static addDamage(_event) {
|
||||
if (!this.action.damage.parts) return;
|
||||
|
||||
const choices = getUnusedDamageTypes(this.action.damage.parts);
|
||||
const choices = getUnusedDamageTypes(this.action._source.damage.parts);
|
||||
const content = new foundry.data.fields.StringField({
|
||||
label: game.i18n.localize('Damage Type'),
|
||||
choices,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
* @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]}
|
||||
*/
|
||||
static getChangeChoices() {
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty'];
|
||||
const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty', 'DhNPC'];
|
||||
|
||||
const getAllLeaves = (root, group, parentPath = '') => {
|
||||
const leaves = [];
|
||||
|
|
@ -175,6 +175,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
const partContext = await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
case 'details':
|
||||
partContext.isItemEffect = partContext.isItemEffect || this.options.isSetting;
|
||||
const useGeneric = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (data.fromInternal && item?.parent?.uuid === this.actor.uuid) return;
|
||||
|
|
@ -138,8 +139,4 @@ export default class DHEnvironmentSettings extends DHBaseActorSettings {
|
|||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
async _onDropItem(event, item) {
|
||||
console.log(item);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
85
module/applications/sheets-configs/npc-settings.mjs
Normal file
85
module/applications/sheets-configs/npc-settings.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import DHBaseActorSettings from '../sheets/api/actor-setting.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
export default class DHNPCSettings extends DHBaseActorSettings {
|
||||
/**@inheritdoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['npc-settings'],
|
||||
position: { width: 455, height: 'auto' },
|
||||
actions: {},
|
||||
dragDrop: [
|
||||
{ dragSelector: null, dropSelector: '.tab.features' },
|
||||
{ dragSelector: '.feature-item', dropSelector: null }
|
||||
]
|
||||
};
|
||||
|
||||
/**@override */
|
||||
static PARTS = {
|
||||
header: {
|
||||
id: 'header',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/header.hbs'
|
||||
},
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs' },
|
||||
details: {
|
||||
id: 'details',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/details.hbs'
|
||||
},
|
||||
features: {
|
||||
id: 'features',
|
||||
template: 'systems/daggerheart/templates/sheets-settings/npc-settings/features.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'details' }, { id: 'features' }],
|
||||
initial: 'details',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = context.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _onDragStart(event) {
|
||||
const featureItem = event.currentTarget.closest('.feature-item');
|
||||
|
||||
if (featureItem) {
|
||||
const feature = this.actor.items.get(featureItem.id);
|
||||
const featureData = { type: 'Item', uuid: feature.uuid, fromInternal: true };
|
||||
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
|
||||
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
|
||||
}
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
|
||||
|
||||
const item = await fromUuid(data.uuid);
|
||||
if (item?.type === 'feature') {
|
||||
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData = item.toObject();
|
||||
delete itemData._id;
|
||||
|
||||
await this.actor.createEmbeddedDocuments('Item', [itemData]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -188,8 +188,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
|
|||
if (type === 'effect') {
|
||||
const move = foundry.utils.getProperty(this.settings, this.movePath);
|
||||
for (const action of move.actions) {
|
||||
const remainingEffects = action.effects.filter(x => x._id !== id);
|
||||
if (action.effects.length !== remainingEffects.length) {
|
||||
const actionEffects = action.effects ?? [];
|
||||
const remainingEffects = actionEffects.filter(x => x._id !== id);
|
||||
if (actionEffects.length !== remainingEffects.length) {
|
||||
await action.update({
|
||||
effects: remainingEffects.map(x => {
|
||||
const { _id, ...rest } = x;
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ 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 NPC } from './npc.mjs';
|
||||
export { default as Party } from './party.mjs';
|
||||
|
|
|
|||
|
|
@ -31,6 +31,16 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs';
|
|||
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
|
||||
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
|
||||
import FilterMenu from '../../ux/filter-menu.mjs';
|
||||
import { getArmorSources, getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
|
||||
import { getArmorSources, getDocFromElement, getDocFromElementSync, sortBy } from '../../../helpers/utils.mjs';
|
||||
|
||||
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
|
||||
|
||||
|
|
@ -57,6 +57,22 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
],
|
||||
contextMenus: [
|
||||
{
|
||||
handler: CharacterSheet.#getCreationMainContextOptions,
|
||||
selector: '.character-details [data-action="editDoc"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
},
|
||||
{
|
||||
handler: CharacterSheet.#getDomainCardContextOptions,
|
||||
selector: '[data-item-uuid][data-type="domainCard"]',
|
||||
|
|
@ -176,6 +192,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||
element.classList.toggle('disabled', disabled);
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
@ -209,8 +228,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...this.document.system.traits[key],
|
||||
name: game.i18n.localize(CONFIG.DH.ACTOR.abilities[key].name),
|
||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x))
|
||||
label: _loc(CONFIG.DH.ACTOR.abilities[key].label),
|
||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)),
|
||||
isSpellcasting: this.document.system.spellcastModifierTrait?.key === key
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
|
@ -226,6 +246,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.resources.stress.emptyPips =
|
||||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||
|
||||
context.equippedItems = sortBy(
|
||||
this.document.items.filter(i => i.system.equipped && (i.type === 'weapon' || i.usable)),
|
||||
i => (i.type === 'weapon' ? (i.system.secondary ? 1 : 0) : 2)
|
||||
);
|
||||
|
||||
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
|
||||
|
||||
return context;
|
||||
|
|
@ -313,6 +338,56 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
/* Context Menu */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static #getCreationMainContextOptions() {
|
||||
/** Returns true if the item is managed by the level up wizard. Such items shouldn't allow things like manual removal */
|
||||
function isItemWizardManaged(item) {
|
||||
const actor = item?.actor;
|
||||
if (!actor) return false;
|
||||
|
||||
// If levelup automation is off in general or for this character, all items are unmanaged
|
||||
// This is disabled until we have proper granted feature removal, for now this feature is to correct errors
|
||||
// const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
|
||||
// if (!levelupAuto) return false;
|
||||
|
||||
// Core items aren't part of levelup data. TODO: add some way to flag a specific character as no auto leveling
|
||||
const classPair = actor.system.class;
|
||||
const coreItems = [actor.system.ancestry, actor.system.community, classPair?.value, classPair?.subclass];
|
||||
if (coreItems.includes(item)) return true;
|
||||
|
||||
const levelups = Object.values(actor.system.levelData?.levelups) ?? [];
|
||||
const uuid = item.uuid;
|
||||
const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing
|
||||
return levelups.some(data => {
|
||||
if (item.type === 'subclass') {
|
||||
const selectedSubclasses = data.selections.map(s => s.secondaryData?.subclass).filter(s => !!s);
|
||||
return sourceUuid
|
||||
? selectedSubclasses.includes(sourceUuid)
|
||||
: selectedSubclasses.length && item.system.isMulticlass;
|
||||
}
|
||||
|
||||
const matchesCard = data.achievements.domainCards.some(i => i.itemUuid === uuid);
|
||||
const matchesSelection = data.selections.some(s => s.itemUuid === uuid);
|
||||
return matchesCard || matchesSelection;
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !isItemWizardManaged(doc);
|
||||
},
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
else return doc.deleteDialog();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for DomainCards.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
|
|
@ -329,7 +404,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async target => {
|
||||
onClick: async (_, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||
if (actorLoadout.available) return doc.update({ 'system.inVault': false });
|
||||
|
|
@ -343,7 +418,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||
if (!actorLoadout.available) {
|
||||
|
|
@ -382,7 +457,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !doc.system.inVault;
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
|
|
@ -408,7 +483,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc.isOwner && doc && !doc.system.equipped;
|
||||
},
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
},
|
||||
{
|
||||
label: 'unequip',
|
||||
|
|
@ -417,7 +492,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc.isOwner && doc && doc.system.equipped;
|
||||
},
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
|
|
@ -712,7 +787,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
? {
|
||||
'system.linkedClass.uuid': {
|
||||
key: 'system.linkedClass.uuid',
|
||||
value: this.document.system.class.value._stats.compendiumSource
|
||||
value: this.document.system.class.value?._stats.compendiumSource
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
|
|
@ -978,7 +1053,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip',
|
||||
cssClass: 'bordered-tooltip dh-style',
|
||||
direction: 'DOWN'
|
||||
});
|
||||
|
||||
|
|
@ -1074,7 +1149,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
game.tooltip.activate(target, {
|
||||
html,
|
||||
locked: true,
|
||||
cssClass: 'bordered-tooltip',
|
||||
cssClass: 'bordered-tooltip dh-style',
|
||||
direction: 'DOWN',
|
||||
noOffset: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,17 @@ export default class DhCompanionSheet extends DHBaseActorSheet {
|
|||
toggleStress: DhCompanionSheet.#toggleStress,
|
||||
actionRoll: DhCompanionSheet.#actionRoll,
|
||||
levelManagement: DhCompanionSheet.#levelManagement
|
||||
}
|
||||
},
|
||||
contextMenus: [
|
||||
{
|
||||
handler: DHBaseActorSheet.getBaseAttackContextOptions,
|
||||
selector: '[data-item-uuid][data-type="attack"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
fixed: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
|
|
|||
136
module/applications/sheets/actors/npc.mjs
Normal file
136
module/applications/sheets/actors/npc.mjs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import DHBaseActorSheet from '../api/base-actor.mjs';
|
||||
|
||||
export default class NPCSheet extends DHBaseActorSheet {
|
||||
/** @inheritDoc */
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ['npc'],
|
||||
position: { width: 660, height: 600 },
|
||||
window: { resizable: true },
|
||||
actions: {},
|
||||
window: {
|
||||
resizable: true,
|
||||
controls: [
|
||||
{
|
||||
icon: 'fa-solid fa-signature',
|
||||
label: 'DAGGERHEART.UI.Tooltip.configureAttribution',
|
||||
action: 'editAttribution'
|
||||
}
|
||||
]
|
||||
},
|
||||
dragDrop: [
|
||||
{
|
||||
dragSelector: '[data-item-id][draggable="true"], [data-item-id] [draggable="true"]',
|
||||
dropSelector: null
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
header: { template: 'systems/daggerheart/templates/sheets/actors/npc/header.hbs' },
|
||||
tabs: { template: 'systems/daggerheart/templates/sheets/actors/npc/navigation.hbs' },
|
||||
features: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/npc/features.hbs',
|
||||
scrollable: ['.feature-section']
|
||||
},
|
||||
notes: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/npc/notes.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [{ id: 'notes' }, { id: 'features' }],
|
||||
initial: 'notes',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
_prepareTabs(group) {
|
||||
const result = super._prepareTabs(group);
|
||||
if (group === 'primary') {
|
||||
result.features.empty = this.document.system.features.length === 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
case 'header':
|
||||
await this._prepareHeaderContext(context, options);
|
||||
break;
|
||||
case 'features':
|
||||
await this._prepareFeaturesContext(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 Features part.
|
||||
* @param {ApplicationRenderContext} context
|
||||
* @param {ApplicationRenderOptions} options
|
||||
* @returns {Promise<void>}
|
||||
* @protected
|
||||
*/
|
||||
async _prepareFeaturesContext(context, _options) {
|
||||
const featureForms = ['passive', 'action', 'reaction'];
|
||||
context.features = this.document.system.features.sort((a, b) =>
|
||||
a.system.featureForm !== b.system.featureForm
|
||||
? featureForms.indexOf(a.system.featureForm) - featureForms.indexOf(b.system.featureForm)
|
||||
: a.sort - b.sort
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
actions: {
|
||||
openDocument: Party.#openDocument,
|
||||
deletePartyMember: Party.#deletePartyMember,
|
||||
deleteItem: Party.#deleteItem,
|
||||
toggleHope: Party.#toggleHope,
|
||||
toggleHitPoints: Party.#toggleHitPoints,
|
||||
toggleStress: Party.#toggleStress,
|
||||
|
|
@ -44,12 +43,10 @@ export default class Party extends DHBaseActorSheet {
|
|||
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' },
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// projects: {
|
||||
// template: 'systems/daggerheart/templates/sheets/actors/party/projects.hbs',
|
||||
// scrollable: ['']
|
||||
// },
|
||||
partyMembers: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/party/party-members.hbs',
|
||||
scrollable: ['']
|
||||
},
|
||||
inventory: {
|
||||
template: 'systems/daggerheart/templates/sheets/actors/party/inventory.hbs',
|
||||
scrollable: ['.tab.inventory .items-section']
|
||||
|
|
@ -60,19 +57,13 @@ export default class Party extends DHBaseActorSheet {
|
|||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
primary: {
|
||||
tabs: [
|
||||
{ id: 'partyMembers' },
|
||||
/* NOT YET IMPLEMENTED */
|
||||
// { id: 'projects' },
|
||||
{ id: 'inventory' },
|
||||
{ id: 'notes' }
|
||||
],
|
||||
tabs: [{ id: 'partyMembers' }, { id: 'inventory' }, { id: 'notes' }],
|
||||
initial: 'partyMembers',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
|
||||
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
|
||||
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary', 'npc'];
|
||||
static DICE_ROLL_ACTOR_TYPES = ['character'];
|
||||
|
||||
async _onRender(context, options) {
|
||||
|
|
@ -85,6 +76,14 @@ export default class Party extends DHBaseActorSheet {
|
|||
/* Prepare Context */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
||||
context.showStats =
|
||||
settings.hidePartyStats === 'never' || (settings.hidePartyStats === 'players' && game.user.isGM);
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
|
|
@ -498,23 +497,4 @@ export default class Party extends DHBaseActorSheet {
|
|||
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export default function DHApplicationMixin(Base) {
|
|||
classes: ['daggerheart', 'sheet', 'dh-style'],
|
||||
actions: {
|
||||
triggerContextMenu: DHSheetV2.#triggerContextMenu,
|
||||
createDoc: DHSheetV2.#createDoc,
|
||||
createDoc: DHSheetV2.#onCreateDoc,
|
||||
editDoc: DHSheetV2.#editDoc,
|
||||
deleteDoc: DHSheetV2.#deleteDoc,
|
||||
toChat: DHSheetV2.#toChat,
|
||||
|
|
@ -97,8 +97,8 @@ export default function DHApplicationMixin(Base) {
|
|||
viewItem: DHSheetV2.#viewItem,
|
||||
toggleEffect: DHSheetV2.#toggleEffect,
|
||||
toggleExtended: DHSheetV2.#toggleExtended,
|
||||
addNewItem: DHSheetV2.#addNewItem,
|
||||
browseItem: DHSheetV2.#browseItem,
|
||||
addNewItem: DHSheetV2.#onAddNewItem,
|
||||
browseItem: DHSheetV2.#onBrowseItem,
|
||||
editAttribution: DHSheetV2.#editAttribution,
|
||||
configureLevelUpOptions: DHSheetV2.#configureLevelUpOptions
|
||||
},
|
||||
|
|
@ -439,7 +439,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const target = element.closest('[data-item-uuid]');
|
||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: true })
|
||||
},
|
||||
{
|
||||
label: 'enableEffect',
|
||||
|
|
@ -448,7 +448,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: false })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
|
|
@ -493,7 +493,9 @@ export default function DHApplicationMixin(Base) {
|
|||
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||
);
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
||||
onClick: async (_, target) => {
|
||||
return (await getDocFromElement(target)).sheet.render({ force: true });
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -508,7 +510,7 @@ export default function DHApplicationMixin(Base) {
|
|||
!foundry.utils.isEmpty(doc?.damage?.parts);
|
||||
return doc?.isOwner && hasDamage;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
const config = action.prepareConfig(event);
|
||||
|
|
@ -528,7 +530,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||
},
|
||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
||||
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -536,7 +538,7 @@ export default function DHApplicationMixin(Base) {
|
|||
options.push({
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
});
|
||||
|
||||
if (deletable)
|
||||
|
|
@ -546,9 +548,9 @@ export default function DHApplicationMixin(Base) {
|
|||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && target.dataset.itemType !== 'beastform';
|
||||
return doc?.isOwner !== false && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
else return doc.deleteDialog();
|
||||
|
|
@ -654,7 +656,7 @@ export default function DHApplicationMixin(Base) {
|
|||
/* Application Clicks Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static async #addNewItem(event, target) {
|
||||
static async #onAddNewItem(event, target) {
|
||||
const createChoice = await foundry.applications.api.DialogV2.wait({
|
||||
classes: ['dh-style', 'two-big-buttons'],
|
||||
buttons: [
|
||||
|
|
@ -673,11 +675,11 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
if (!createChoice) return;
|
||||
|
||||
if (createChoice === 'browse') return DHSheetV2.#browseItem.call(this, event, target);
|
||||
else return DHSheetV2.#createDoc.call(this, event, target);
|
||||
if (createChoice === 'browse') return DHSheetV2.#onBrowseItem.call(this, event, target);
|
||||
else return DHSheetV2.#onCreateDoc.call(this, event, target);
|
||||
}
|
||||
|
||||
static async #browseItem(event, target) {
|
||||
static async #onBrowseItem(_event, target) {
|
||||
const type = target.dataset.compendium ?? target.dataset.type;
|
||||
|
||||
const presets = {
|
||||
|
|
@ -732,7 +734,7 @@ export default function DHApplicationMixin(Base) {
|
|||
* Create an embedded document.
|
||||
* @type {ApplicationClickAction}
|
||||
*/
|
||||
static async #createDoc(event, target) {
|
||||
static async #onCreateDoc(event, target) {
|
||||
const { documentClass, type, inVault, disabled } = target.dataset;
|
||||
const parentIsItem = this.document.documentName === 'Item';
|
||||
const featureOnCharacter = this.document.parent?.type === 'character' && type === 'feature';
|
||||
|
|
@ -760,11 +762,15 @@ export default function DHApplicationMixin(Base) {
|
|||
type,
|
||||
system: systemData
|
||||
};
|
||||
|
||||
if (inVault) data['system.inVault'] = true;
|
||||
if (disabled) data.disabled = true;
|
||||
if (type === 'domainCard' && parent?.system.domains?.length) {
|
||||
data.system.domain = parent.system.domains[0];
|
||||
|
||||
if (type === 'domainCard') {
|
||||
if (parent?.system.domains?.length) data.system.domain = parent.system.domains[0];
|
||||
if (inVault) data.system.inVault = true;
|
||||
} else if (type === 'weapon') {
|
||||
// Passing an empty system object to weapon causes validation failure due to attack action initialization
|
||||
// todo: determine why, fix it at its source, then remove this fallback
|
||||
delete data.system;
|
||||
}
|
||||
|
||||
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||
|
|
|
|||
|
|
@ -166,6 +166,15 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Add support for input content editables */
|
||||
_toggleDisabled(disabled) {
|
||||
super._toggleDisabled(disabled);
|
||||
const form = this.form;
|
||||
for (const element of form.querySelectorAll('.input[contenteditable]')) {
|
||||
element.classList.toggle('disabled', disabled);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Context Menu */
|
||||
/* -------------------------------------------- */
|
||||
|
|
@ -180,6 +189,43 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
|
|||
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the set of ContextMenu options for the base attack.
|
||||
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
|
||||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static getBaseAttackContextOptions() {
|
||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
return [
|
||||
{
|
||||
label: 'DAGGERHEART.CONFIG.RollTypes.attack.name',
|
||||
icon: 'fa-solid fa-burst',
|
||||
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
const config = action.prepareConfig(event);
|
||||
config.effects = await game.system.api.data.actions.actionsTypes.base.getEffects(
|
||||
this.document,
|
||||
doc
|
||||
);
|
||||
config.hasRoll = false;
|
||||
return action && action.workflow.get('damage').execute(config, null, true);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Application Listener Actions */
|
||||
/* -------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
callback: async target => {
|
||||
onClick: async (_, target) => {
|
||||
const feature = await getDocFromElement(target);
|
||||
if (!feature) return;
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
|
|
|
|||
|
|
@ -104,9 +104,10 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.domains = this.document.system.domains;
|
||||
context.subclasses = await this.document.system.fetchSubclasses();
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -128,20 +129,8 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (itemType === 'subclass') {
|
||||
if (item.system.linkedClass) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.subclassAlreadyLinked', {
|
||||
name: item.name,
|
||||
class: this.document.name
|
||||
})
|
||||
);
|
||||
}
|
||||
await item.update({ 'system.linkedClass': this.document.uuid });
|
||||
await this.document.update({
|
||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
} else if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||
|
||||
if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||
super._onDrop(event);
|
||||
} else if (this.document.parent?.type !== 'character') {
|
||||
if (itemType === 'weapon') {
|
||||
|
|
@ -200,12 +189,6 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
static async #removeItemFromCollection(_event, element) {
|
||||
const { uuid, target } = element.dataset;
|
||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||
|
||||
if (target === 'subclasses') {
|
||||
const subclass = await foundry.utils.fromUuid(uuid);
|
||||
await subclass?.update({ 'system.linkedClass': null });
|
||||
}
|
||||
|
||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i && i.uuid !== uuid).map(x => x.uuid) });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,4 +40,36 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
get relatedDocs() {
|
||||
return this.document.system.features.map(x => x.item);
|
||||
}
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (this.document.system.linkedClass) {
|
||||
const classData = await fromUuid(this.document.system.linkedClass);
|
||||
context.class = classData ?? {
|
||||
name: _loc('DAGGERHEART.GENERAL.missingX', { x: _loc('TYPES.Item.class') }),
|
||||
missing: true
|
||||
};
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
if (itemType === 'class') {
|
||||
const uuid = item._stats.compendiumSource ?? item.uuid;
|
||||
if (this.document.system.linkedClass !== uuid) {
|
||||
await this.document.update({ 'system.linkedClass': uuid });
|
||||
// Re-render all class sheets for instant feedback
|
||||
for (const app of foundry.applications.instances.values()) {
|
||||
if (app.document?.type === 'class') app.render();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return super._onDrop(event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,19 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
{
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollActionRoll',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
visible: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
return message.system.hasRoll && (game.user.isGM || message.isAuthor);
|
||||
},
|
||||
callback: async li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const reroll = await message.rolls[0].reroll({ liveRoll: true });
|
||||
message.update({ rolls: [reroll] });
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
|
|
@ -113,9 +126,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
: false;
|
||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||
},
|
||||
callback: li => {
|
||||
callback: async li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
|
||||
const update = await message.system.getRerolledDamage();
|
||||
message.update(update);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
async _prepareTrackerContext(context, options) {
|
||||
await super._prepareTrackerContext(context, options);
|
||||
|
||||
const adversaries = context.turns?.filter(x => x.isNPC) ?? [];
|
||||
const npcs = context.turns?.filter(x => x.isNPC) ?? [];
|
||||
const adversaries = npcs.filter(x => x.disposition !== CONST.TOKEN_DISPOSITIONS.FRIENDLY);
|
||||
const friendlies = npcs.filter(x => x.disposition === CONST.TOKEN_DISPOSITIONS.FRIENDLY);
|
||||
const characters = context.turns?.filter(x => !x.isNPC) ?? [];
|
||||
const spotlightQueueEnabled = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
|
|
@ -75,25 +77,56 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
Object.assign(context, {
|
||||
actionTokens: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.variantRules).actionTokens,
|
||||
adversaries,
|
||||
friendlies,
|
||||
allCharacters: characters,
|
||||
characters: characters.filter(x => !spotlightQueueEnabled || x.system.spotlight.requestOrderIndex == 0),
|
||||
spotlightRequests
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the dialog used to edit the name of the currently viewed Combat encounter.
|
||||
* @this {CombatTracker}
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async #onEditName() {
|
||||
const combat = this.viewed;
|
||||
if (!combat || !game.user.isGM) return null;
|
||||
const field = combat.schema.fields.name;
|
||||
const inputHTML = field.toFormGroup({}, { name: 'name', value: combat.name, autofocus: true }).outerHTML;
|
||||
const formData = await foundry.applications.api.DialogV2.input({
|
||||
window: { icon: 'fa-solid fa-tag', title: 'COMBAT.ACTIONS.EditNameTitle' },
|
||||
position: { width: 480 },
|
||||
content: inputHTML
|
||||
});
|
||||
await combat.update({ name: formData.name || '' });
|
||||
}
|
||||
|
||||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
label: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
label: 'COMBAT.ACTIONS.EditName',
|
||||
icon: 'fa-solid fa-tag',
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
onClick: () => DhCombatTracker.#onEditName.call(this)
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
label: 'COMBAT.ACTIONS.LinkToScene',
|
||||
icon: '<i class="fa-solid fa-link"></i>',
|
||||
visible: () => game.user.isGM && !this.scene,
|
||||
onClick: () => this.viewed.toggleSceneLink()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.ACTIONS.UnlinkFromScene',
|
||||
icon: '<i class="fa-solid fa-unlink"></i>',
|
||||
visible: () => game.user.isGM && !!this.scene,
|
||||
onClick: () => this.viewed.toggleSceneLink()
|
||||
},
|
||||
{
|
||||
label: 'COMBAT.End',
|
||||
icon: 'fa-solid fa-xmark',
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
onClick: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
@ -129,7 +162,8 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
active: index === combat.turn,
|
||||
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
|
||||
type: combatant.actor?.system?.type,
|
||||
img: await this._getCombatantThumbnail(combatant)
|
||||
img: await this._getCombatantThumbnail(combatant),
|
||||
disposition: combatant.token?.disposition
|
||||
};
|
||||
|
||||
turn.css = [turn.active ? 'active' : null, hidden ? 'hide' : null, isDefeated ? 'defeated' : null].filterJoin(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { DhCountdown } from '../../data/countdowns.mjs';
|
||||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ export default class CountdownEdit extends HandlebarsApplicationMixin(Applicatio
|
|||
}
|
||||
|
||||
await this.data.updateSource(update);
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, this.gmSetSetting.bind(this.data), this.data, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { waitForDiceSoNice } from '../../helpers/utils.mjs';
|
||||
import { emitAsGM, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent, RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -21,19 +21,19 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
static DEFAULT_OPTIONS = {
|
||||
id: 'countdowns',
|
||||
tag: 'div',
|
||||
classes: ['daggerheart', 'dh-style', 'countdowns', 'faded-ui'],
|
||||
classes: ['daggerheart', 'dh-style', 'countdowns'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-clock-rotate-left',
|
||||
frame: true,
|
||||
frame: false,
|
||||
title: 'DAGGERHEART.UI.Countdowns.title',
|
||||
positioned: false,
|
||||
resizable: false,
|
||||
minimizable: false
|
||||
},
|
||||
actions: {
|
||||
toggleViewMode: DhCountdowns.#toggleViewMode,
|
||||
editCountdowns: DhCountdowns.#editCountdowns,
|
||||
loopCountdown: DhCountdowns.#loopCountdown,
|
||||
toggleViewMode: DhCountdowns.#onToggleViewMode,
|
||||
editCountdowns: DhCountdowns.#onEditCountdowns,
|
||||
loopCountdown: DhCountdowns.#onLoopCountdown,
|
||||
decreaseCountdown: (_, target) => this.editCountdown(false, target),
|
||||
increaseCountdown: (_, target) => this.editCountdown(true, target)
|
||||
},
|
||||
|
|
@ -62,20 +62,6 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
if (iconOnly) frame.classList.add('icon-only');
|
||||
else frame.classList.remove('icon-only');
|
||||
|
||||
const header = frame.querySelector('.window-header');
|
||||
header.querySelector('button[data-action="close"]').remove();
|
||||
header.querySelector('button[data-action="toggleControls"]').remove();
|
||||
|
||||
if (game.user.isGM) {
|
||||
const editTooltip = game.i18n.localize('DAGGERHEART.APPLICATIONS.CountdownEdit.editTitle');
|
||||
const editButton = `<a style="margin-right: 8px;" class="header-control" data-tooltip="${editTooltip}" aria-label="${editTooltip}" data-action="editCountdowns"><i class="fa-solid fa-wrench"></i></a>`;
|
||||
header.insertAdjacentHTML('beforeEnd', editButton);
|
||||
}
|
||||
|
||||
const minimizeTooltip = game.i18n.localize('DAGGERHEART.UI.Countdowns.toggleIconMode');
|
||||
const minimizeButton = `<a class="header-control" data-tooltip="${minimizeTooltip}" aria-label="${minimizeTooltip}" data-action="toggleViewMode"><i class="fa-solid fa-down-left-and-up-right-to-center"></i></a>`;
|
||||
header.insertAdjacentHTML('beforeEnd', minimizeButton);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +147,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return true;
|
||||
}
|
||||
|
||||
static async #toggleViewMode() {
|
||||
static async #onToggleViewMode() {
|
||||
const currentMode = game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.countdownMode);
|
||||
const appMode = CONFIG.DH.GENERAL.countdownAppMode;
|
||||
const newMode = currentMode === appMode.textIcon ? appMode.iconOnly : appMode.textIcon;
|
||||
|
|
@ -172,15 +158,16 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
this.render();
|
||||
}
|
||||
|
||||
static async #editCountdowns() {
|
||||
static async #onEditCountdowns() {
|
||||
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||
}
|
||||
|
||||
static async #loopCountdown(_, target) {
|
||||
static async #onLoopCountdown(_, target) {
|
||||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = settings.countdowns[target.id];
|
||||
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||
const countdown = settings.countdowns[countdownId];
|
||||
|
||||
let progressMax = countdown.progress.start;
|
||||
let message = null;
|
||||
|
|
@ -199,12 +186,12 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
|
||||
await waitForDiceSoNice(message);
|
||||
await settings.updateSource({
|
||||
[`countdowns.${target.id}.progress`]: {
|
||||
[`countdowns.${countdownId}.progress`]: {
|
||||
current: newMax,
|
||||
start: newMax
|
||||
}
|
||||
});
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
@ -213,12 +200,13 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
if (!DhCountdowns.canPerformEdit()) return;
|
||||
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Countdowns);
|
||||
const countdown = settings.countdowns[target.id];
|
||||
const countdownId = target.closest('[data-countdown]').dataset.countdown;
|
||||
const countdown = settings.countdowns[countdownId];
|
||||
const newCurrent = increase
|
||||
? Math.min(countdown.progress.current + 1, countdown.progress.start)
|
||||
: Math.max(countdown.progress.current - 1, 0);
|
||||
await settings.updateSource({ [`countdowns.${target.id}.progress.current`]: newCurrent });
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
await settings.updateSource({ [`countdowns.${countdownId}.progress.current`]: newCurrent });
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
@ -277,7 +265,7 @@ export default class DhCountdowns extends HandlebarsApplicationMixin(Application
|
|||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await emitAsGM(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
await emitGMUpdate(GMUpdateEvent.UpdateCountdowns, DhCountdowns.gmSetSetting.bind(settings), settings, null, {
|
||||
refreshType: RefreshType.Countdown
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
|
||||
async updateFear(value) {
|
||||
return emitAsGM(
|
||||
return emitGMUpdate(
|
||||
GMUpdateEvent.UpdateFear,
|
||||
game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
|
||||
value
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { getDocFromElement } from '../../helpers/utils.mjs';
|
||||
import { RefreshType, socketEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||
|
|
@ -47,7 +48,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
expandContent: this.expandContent,
|
||||
resetFilters: this.resetFilters,
|
||||
sortList: this.sortList,
|
||||
openSettings: this.openSettings
|
||||
openSettings: this.openSettings,
|
||||
viewSheet: this.#onViewSheet
|
||||
},
|
||||
position: {
|
||||
left: 100,
|
||||
|
|
@ -109,8 +111,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
CONFIG.DH.id,
|
||||
CONFIG.DH.FLAGS[`${this.compendiumBrowserTypeKey}`].position
|
||||
);
|
||||
|
||||
options.position = userPresetPosition ?? ItemBrowser.DEFAULT_OPTIONS.position;
|
||||
delete options.position.zIndex;
|
||||
|
||||
if (!userPresetPosition) {
|
||||
const width = noFolder === true || lite === true ? 600 : 850;
|
||||
|
|
@ -277,7 +279,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
||||
}
|
||||
|
||||
this.fieldFilter = this._createFieldFilter();
|
||||
this.fieldFilter = await this._createFieldFilter();
|
||||
|
||||
if (this.presets?.filter) {
|
||||
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||
|
|
@ -306,7 +308,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
{
|
||||
items: this.items,
|
||||
menu: this.selectedMenu,
|
||||
formatLabel: this.formatLabel
|
||||
formatLabel: this.formatLabel,
|
||||
viewSheet: this.items[0] instanceof Actor
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -355,12 +358,12 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
);
|
||||
}
|
||||
|
||||
_createFieldFilter() {
|
||||
async _createFieldFilter() {
|
||||
const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters');
|
||||
filters.forEach(f => {
|
||||
for (const f of filters) {
|
||||
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
||||
else if (typeof f.choices === 'function') {
|
||||
f.choices = f.choices(this.items);
|
||||
f.choices = await f.choices(this.items);
|
||||
}
|
||||
|
||||
// Clear field label so template uses our custom label parameter
|
||||
|
|
@ -370,7 +373,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
|
||||
f.name ??= f.key;
|
||||
f.value = this.presets?.filter?.[f.name]?.value ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
|
@ -567,6 +571,11 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
}
|
||||
}
|
||||
|
||||
static async #onViewSheet(_, target) {
|
||||
const document = await getDocFromElement(target);
|
||||
document?.sheet?.render(true);
|
||||
}
|
||||
|
||||
_createDragProcess() {
|
||||
new foundry.applications.ux.DragDrop.implementation({
|
||||
dragSelector: '.item-container',
|
||||
|
|
@ -605,7 +614,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
items: {
|
||||
folder: 'equipments',
|
||||
render: {
|
||||
noFolder: true
|
||||
folders: [
|
||||
'equipments',
|
||||
'ancestries',
|
||||
'classes',
|
||||
'subclasses',
|
||||
'domains',
|
||||
'communities',
|
||||
'beastforms'
|
||||
// excluded: features
|
||||
]
|
||||
}
|
||||
},
|
||||
compendium: {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
import { emitGMUpdate, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhSceneNavigation extends foundry.applications.ui.SceneNavigation {
|
||||
/** @inheritdoc */
|
||||
|
|
@ -68,7 +68,7 @@ export default class DhSceneNavigation extends foundry.applications.ui.SceneNavi
|
|||
1
|
||||
)[0];
|
||||
newEnvironments.unshift(newFirst);
|
||||
emitAsGM(
|
||||
emitGMUpdate(
|
||||
GMUpdateEvent.UpdateDocument,
|
||||
scene.update.bind(scene),
|
||||
{ 'flags.daggerheart.sceneEnvironments': newEnvironments },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue