mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-22 23:43:37 +02:00
Merged with main
This commit is contained in:
commit
4e555dd314
174 changed files with 3707 additions and 1217 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,8 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
|
|||
const resetValue = increasing
|
||||
? 0
|
||||
: feature.system.resource.max
|
||||
? Roll.replaceFormulaData(feature.system.resource.max, this.actor)
|
||||
? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total
|
||||
: 0;
|
||||
|
||||
await feature.update({ 'system.resource.value': resetValue });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
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>');
|
||||
|
|
@ -133,7 +139,10 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.members = {};
|
||||
context.allHaveRolled = Object.keys(this.party.system.tagTeam.members).every(key => {
|
||||
const data = this.party.system.tagTeam.members[key];
|
||||
return Boolean(data.rollData);
|
||||
const hasRolled = Boolean(data.rollData);
|
||||
if (!hasRolled) return false;
|
||||
|
||||
return !data.rollData.options.hasDamage || Boolean(data.rollData.options.damage);
|
||||
});
|
||||
|
||||
return context;
|
||||
|
|
|
|||
|
|
@ -264,7 +264,9 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
key = event.target.closest('[data-key]').dataset.key;
|
||||
if (!this.action[key]) return;
|
||||
|
||||
data[key].push(this.action.defaultValues[key] ?? {});
|
||||
const value = key === 'areas' ? { name: this.action.item.name } : {};
|
||||
|
||||
data[key].push(this.action.defaultValues[key] ?? value);
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,15 +19,17 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
return context;
|
||||
}
|
||||
|
||||
static async addEffect(_event) {
|
||||
static async addEffect(event) {
|
||||
const { areaIndex } = event.target.dataset;
|
||||
if (!this.action.effects) return;
|
||||
const data = this.action.toObject();
|
||||
|
||||
const created = await this.action.item.createEmbeddedDocuments('ActiveEffect', [
|
||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject()
|
||||
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
|
||||
]);
|
||||
|
||||
data.effects.push({ _id: created[0]._id });
|
||||
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(created[0]._id);
|
||||
else data.effects.push({ _id: created[0]._id });
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
this.action.item.effects.get(created[0]._id).sheet.render(true);
|
||||
}
|
||||
|
|
@ -52,9 +54,19 @@ export default class DHActionConfig extends DHActionBaseConfig {
|
|||
|
||||
static removeEffect(event, button) {
|
||||
if (!this.action.effects) return;
|
||||
const index = button.dataset.index,
|
||||
|
||||
const { areaIndex, index } = button.dataset;
|
||||
let effectId = null;
|
||||
if (areaIndex !== undefined) {
|
||||
effectId = this.action.areas[areaIndex].effects[index];
|
||||
const data = this.action.toObject();
|
||||
data.areas[areaIndex].effects.splice(index, 1);
|
||||
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||
} else {
|
||||
effectId = this.action.effects[index]._id;
|
||||
this.constructor.removeElement.bind(this)(event, button);
|
||||
this.constructor.removeElement.call(this, event, button);
|
||||
}
|
||||
|
||||
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,21 +31,35 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
|
|||
}
|
||||
|
||||
static async addEffect(_event) {
|
||||
const { areaIndex } = event.target.dataset;
|
||||
if (!this.action.effects) return;
|
||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject();
|
||||
|
||||
const effectData = game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false });
|
||||
const data = this.action.toObject();
|
||||
|
||||
this.sheetUpdate(data, effectData);
|
||||
this.effects = [...this.effects, effectData];
|
||||
data.effects.push({ _id: effectData.id });
|
||||
|
||||
if (areaIndex !== undefined) data.areas[areaIndex].effects.push(effectData.id);
|
||||
else data.effects.push({ _id: effectData.id });
|
||||
|
||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||
}
|
||||
|
||||
static removeEffect(event, button) {
|
||||
if (!this.action.effects) return;
|
||||
const index = button.dataset.index,
|
||||
const { areaIndex, index } = button.dataset;
|
||||
let effectId = null;
|
||||
if (areaIndex !== undefined) {
|
||||
effectId = this.action.areas[areaIndex].effects[index];
|
||||
const data = this.action.toObject();
|
||||
data.areas[areaIndex].effects.splice(index, 1);
|
||||
this.constructor.updateForm.call(this, null, null, { object: foundry.utils.flattenObject(data) });
|
||||
} else {
|
||||
effectId = this.action.effects[index]._id;
|
||||
this.constructor.removeElement.bind(this)(event, button);
|
||||
this.constructor.removeElement.call(this, event, button);
|
||||
}
|
||||
|
||||
this.sheetUpdate(
|
||||
this.action.toObject(),
|
||||
this.effects.find(x => x.id === effectId),
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
static DEFAULT_OPTIONS = {
|
||||
classes: ['character'],
|
||||
position: { width: 850, height: 800 },
|
||||
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
|
||||
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
|
||||
actions: {
|
||||
toggleVault: CharacterSheet.#toggleVault,
|
||||
rollAttribute: CharacterSheet.#rollAttribute,
|
||||
|
|
@ -68,7 +66,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
},
|
||||
{
|
||||
handler: CharacterSheet.#getEquipamentContextOptions,
|
||||
handler: CharacterSheet.#getEquipmentContextOptions,
|
||||
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
|
||||
options: {
|
||||
parentClassHooks: false,
|
||||
|
|
@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
return applicationOptions;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
_toggleDisabled(disabled) {
|
||||
// Overriden to only disable text inputs by default.
|
||||
// Everything else is done by checking @root.editable in the sheet
|
||||
const form = this.form;
|
||||
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
|
|
@ -315,11 +323,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
const options = [
|
||||
{
|
||||
name: 'toLoadout',
|
||||
label: 'toLoadout',
|
||||
icon: 'fa-solid fa-arrow-up',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system.inVault;
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async target => {
|
||||
const doc = await getDocFromElement(target);
|
||||
|
|
@ -329,11 +337,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'recall',
|
||||
label: 'recall',
|
||||
icon: 'fa-solid fa-bolt-lightning',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system.inVault;
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
|
|
@ -368,17 +376,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'toVault',
|
||||
label: 'toVault',
|
||||
icon: 'fa-solid fa-arrow-down',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && !doc.system.inVault;
|
||||
return doc?.isOwner && !doc.system.inVault;
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
* @this {CharacterSheet}
|
||||
* @protected
|
||||
*/
|
||||
static #getEquipamentContextOptions() {
|
||||
static #getEquipmentContextOptions() {
|
||||
const options = [
|
||||
{
|
||||
name: 'equip',
|
||||
label: 'equip',
|
||||
icon: 'fa-solid fa-hands',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && !doc.system.equipped;
|
||||
return doc.isOwner && doc && !doc.system.equipped;
|
||||
},
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
},
|
||||
{
|
||||
name: 'unequip',
|
||||
label: 'unequip',
|
||||
icon: 'fa-solid fa-hands',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system.equipped;
|
||||
return doc.isOwner && doc && doc.system.equipped;
|
||||
},
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -433,18 +433,18 @@ export default function DHApplicationMixin(Base) {
|
|||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||
const options = [
|
||||
{
|
||||
name: 'disableEffect',
|
||||
label: 'disableEffect',
|
||||
icon: 'fa-solid fa-lightbulb',
|
||||
condition: element => {
|
||||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||
},
|
||||
{
|
||||
name: 'enableEffect',
|
||||
label: 'enableEffect',
|
||||
icon: 'fa-regular fa-lightbulb',
|
||||
condition: element => {
|
||||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
|
|
@ -452,7 +452,7 @@ export default function DHApplicationMixin(Base) {
|
|||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
|
||||
label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
|
||||
icon: `<i class="${option.icon}"></i>`
|
||||
}));
|
||||
|
||||
|
|
@ -483,14 +483,14 @@ export default function DHApplicationMixin(Base) {
|
|||
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
|
||||
const options = [
|
||||
{
|
||||
name: 'CONTROLS.CommonEdit',
|
||||
label: 'CONTROLS.CommonEdit',
|
||||
icon: 'fa-solid fa-pen-to-square',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const { dataset } = target.closest('[data-item-uuid]');
|
||||
const doc = getDocFromElementSync(target);
|
||||
return (
|
||||
(!dataset.noCompendiumEdit && !doc) ||
|
||||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||
);
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
||||
|
|
@ -499,14 +499,14 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
if (usable) {
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.GENERAL.damage',
|
||||
label: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return (
|
||||
const hasDamage =
|
||||
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
|
||||
!foundry.utils.isEmpty(doc?.damage?.parts)
|
||||
);
|
||||
!foundry.utils.isEmpty(doc?.damage?.parts);
|
||||
return doc?.isOwner && hasDamage;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
|
|
@ -522,11 +522,11 @@ export default function DHApplicationMixin(Base) {
|
|||
});
|
||||
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||
icon: 'fa-solid fa-burst',
|
||||
condition: target => {
|
||||
visible: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||
},
|
||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
||||
});
|
||||
|
|
@ -534,18 +534,19 @@ export default function DHApplicationMixin(Base) {
|
|||
|
||||
if (toChat)
|
||||
options.push({
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
});
|
||||
|
||||
if (deletable)
|
||||
options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
label: 'CONTROLS.CommonDelete',
|
||||
icon: 'fa-solid fa-trash',
|
||||
condition: element => {
|
||||
visible: element => {
|
||||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.itemType !== 'beastform';
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default class FeatureSheet extends DHBaseItemSheet {
|
|||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
};
|
||||
//Might be wrong location but testing out if here is okay.
|
||||
//Might be wrong location but testing out if here is okay.
|
||||
/**@override */
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
const options = super._getEntryContextOptions();
|
||||
options.push(
|
||||
{
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
|
||||
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor?.type === 'adversary' && actor.system.type !== 'social';
|
||||
},
|
||||
|
|
@ -92,9 +92,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
|
|||
}
|
||||
},
|
||||
{
|
||||
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
|
||||
label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
|
||||
icon: `<i class="fa-regular fa-square"></i>`,
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const actor = game.actors.get(li.dataset.entryId);
|
||||
return actor && actor.type === 'party' && !actor.system.active;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -103,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
// {
|
||||
// name: 'Reroll',
|
||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
||||
// condition: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
|
||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
||||
// },
|
||||
// callback: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
||||
// }
|
||||
// },
|
||||
{
|
||||
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'),
|
||||
label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
condition: li => {
|
||||
visible: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const hasRolledDamage = message.system.hasDamage
|
||||
? Object.keys(message.system.damage).length > 0
|
||||
|
|
|
|||
|
|
@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
|
|||
_getCombatContextOptions() {
|
||||
return [
|
||||
{
|
||||
name: 'COMBAT.ClearMovementHistories',
|
||||
label: 'COMBAT.ClearMovementHistories',
|
||||
icon: '<i class="fa-solid fa-shoe-prints"></i>',
|
||||
condition: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
|
||||
callback: () => this.viewed.clearMovementHistories()
|
||||
},
|
||||
{
|
||||
name: 'COMBAT.Delete',
|
||||
label: 'COMBAT.Delete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
condition: () => game.user.isGM && !!this.viewed,
|
||||
visible: () => game.user.isGM && !!this.viewed,
|
||||
callback: () => this.viewed.endCombat()
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,8 +6,11 @@ export default class DHContextMenu extends foundry.applications.ux.ContextMenu {
|
|||
static triggerContextMenu(event, altSelector) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { clientX, clientY } = event;
|
||||
|
||||
const selector = altSelector ?? '[data-item-uuid]';
|
||||
if (ui.context?.selector === selector) return;
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
const target = event.target.closest(selector) ?? event.currentTarget.closest(selector);
|
||||
target?.dispatchEvent(
|
||||
new PointerEvent('contextmenu', {
|
||||
|
|
|
|||
|
|
@ -95,4 +95,61 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
|
|||
});
|
||||
return inBounds.length === 1 ? inBounds[0] : null;
|
||||
}
|
||||
|
||||
static getTemplateShape({ type, angle, range, direction } = {}) {
|
||||
const { line, rectangle, inFront, cone, circle, emanation } = CONFIG.DH.GENERAL.templateTypes;
|
||||
|
||||
/* Length calculation */
|
||||
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
||||
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
||||
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
||||
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
||||
|
||||
const settings = canvas.scene?.rangeSettings;
|
||||
const rangeNumber = Number(range);
|
||||
const length = (!Number.isNaN(rangeNumber) ? rangeNumber : settings ? settings[range] : 0) * dimensionConstant;
|
||||
/*----*/
|
||||
|
||||
const shapeData = {
|
||||
...canvas.mousePosition,
|
||||
type: type,
|
||||
direction: direction ?? 0
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case rectangle.id:
|
||||
shapeData.width = length;
|
||||
shapeData.height = length;
|
||||
break;
|
||||
case line.id:
|
||||
shapeData.length = length;
|
||||
shapeData.width = 5 * dimensionConstant;
|
||||
break;
|
||||
case cone.id:
|
||||
shapeData.angle = angle ?? CONFIG.MeasuredTemplate.defaults.angle;
|
||||
shapeData.radius = length;
|
||||
break;
|
||||
case inFront.id:
|
||||
shapeData.angle = '180';
|
||||
shapeData.radius = length;
|
||||
shapeData.type = cone.id;
|
||||
break;
|
||||
case circle.id:
|
||||
shapeData.radius = length;
|
||||
break;
|
||||
case emanation.id:
|
||||
shapeData.radius = length;
|
||||
shapeData.base = {
|
||||
type: 'token',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return shapeData;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,3 +115,10 @@ export const advantageState = {
|
|||
value: 1
|
||||
}
|
||||
};
|
||||
|
||||
export const areaTypes = {
|
||||
placed: {
|
||||
id: 'placed',
|
||||
label: 'Placed Area'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -80,12 +80,30 @@ export const groupAttackRange = {
|
|||
|
||||
/* circle|cone|rect|ray used to be CONST.MEASURED_TEMPLATE_TYPES. Hardcoded for now */
|
||||
export const templateTypes = {
|
||||
CIRCLE: 'circle',
|
||||
CONE: 'cone',
|
||||
RECTANGLE: 'rectangle',
|
||||
LINE: 'line',
|
||||
EMANATION: 'emanation',
|
||||
INFRONT: 'inFront'
|
||||
circle: {
|
||||
id: 'circle',
|
||||
label: 'Circle'
|
||||
},
|
||||
cone: {
|
||||
id: 'cone',
|
||||
label: 'Cone'
|
||||
},
|
||||
rectangle: {
|
||||
id: 'rectangle',
|
||||
label: 'Rectangle'
|
||||
},
|
||||
line: {
|
||||
id: 'line',
|
||||
label: 'Line'
|
||||
},
|
||||
emanation: {
|
||||
id: 'emanation',
|
||||
label: 'Emanation'
|
||||
},
|
||||
inFront: {
|
||||
id: 'inFront',
|
||||
label: 'In Front'
|
||||
}
|
||||
};
|
||||
|
||||
export const rangeInclusion = {
|
||||
|
|
@ -1092,3 +1110,18 @@ export const fallAndCollisionDamage = {
|
|||
damageFormula: '1d20 + 5'
|
||||
}
|
||||
};
|
||||
|
||||
export const simpleDispositions = {
|
||||
[-1]: {
|
||||
id: -1,
|
||||
label: 'TOKEN.DISPOSITION.HOSTILE'
|
||||
},
|
||||
[0]: {
|
||||
id: 0,
|
||||
label: 'TOKEN.DISPOSITION.NEUTRAL'
|
||||
},
|
||||
[1]: {
|
||||
id: 1,
|
||||
label: 'TOKEN.DISPOSITION.FRIENDLY'
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,12 +75,17 @@ export const typeConfig = {
|
|||
{
|
||||
key: 'type',
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => type ? `TYPES.Item.${type}` : '-'
|
||||
format: type => (type ? `TYPES.Item.${type}` : '-')
|
||||
},
|
||||
{
|
||||
key: 'system.secondary',
|
||||
label: 'DAGGERHEART.UI.ItemBrowser.subtype',
|
||||
format: isSecondary => (isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-')
|
||||
format: isSecondary =>
|
||||
isSecondary
|
||||
? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short'
|
||||
: isSecondary === false
|
||||
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
|
||||
: '-'
|
||||
},
|
||||
{
|
||||
key: 'system.tier',
|
||||
|
|
@ -260,12 +265,12 @@ export const typeConfig = {
|
|||
{
|
||||
key: 'system.type',
|
||||
label: 'DAGGERHEART.GENERAL.type',
|
||||
format: type => type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-'
|
||||
format: type => (type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-')
|
||||
},
|
||||
{
|
||||
key: 'system.domain',
|
||||
label: 'DAGGERHEART.GENERAL.Domain.single',
|
||||
format: domain => domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-'
|
||||
format: domain => (domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-')
|
||||
},
|
||||
{
|
||||
key: 'system.level',
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export const gameSettings = {
|
|||
SpotlightRequestQueue: 'SpotlightRequestQueue',
|
||||
CompendiumBrowserSettings: 'CompendiumBrowserSettings',
|
||||
SpotlightTracker: 'SpotlightTracker',
|
||||
ActiveParty: 'ActiveParty',
|
||||
ActiveParty: 'ActiveParty'
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
|
|
|
|||
|
|
@ -15,3 +15,4 @@ export * as chatMessages from './chat-message/_modules.mjs';
|
|||
export * as fields from './fields/_module.mjs';
|
||||
export * as items from './item/_module.mjs';
|
||||
export * as scenes from './scene/_module.mjs';
|
||||
export * as regionBehaviors from './regionBehavior/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
if (!!this.item?.system?.attack) {
|
||||
if (this.damage.includeBase) {
|
||||
const baseDamage = this.getParentDamage();
|
||||
this.damage.parts.unshift(new DHDamageData(baseDamage));
|
||||
this.damage.parts.hitPoints = new DHDamageData(baseDamage);
|
||||
}
|
||||
if (this.roll.useDefault) {
|
||||
this.roll.trait = this.item.system.attack.roll.trait;
|
||||
|
|
@ -51,7 +51,7 @@ export default class DHAttackAction extends DHDamageAction {
|
|||
async use(event, options) {
|
||||
const result = await super.use(event, options);
|
||||
|
||||
if (result?.message?.system.action.roll?.type === 'attack') {
|
||||
if (result?.message?.system.action?.roll?.type === 'attack') {
|
||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.characterAttack.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ const fields = foundry.data.fields;
|
|||
*/
|
||||
|
||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||
static extraSchemas = ['cost', 'uses', 'range'];
|
||||
static extraSchemas = ['areas', 'cost', 'uses', 'range'];
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
|
|
@ -110,6 +110,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return this._id;
|
||||
}
|
||||
|
||||
/** Returns true if the current user is the owner of the containing item */
|
||||
get isOwner() {
|
||||
return this.item?.isOwner ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Item the action is attached too.
|
||||
*/
|
||||
|
|
@ -143,6 +148,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
: null;
|
||||
}
|
||||
|
||||
/** Returns true if the action is usable */
|
||||
get usable() {
|
||||
const actor = this.actor;
|
||||
return this.isOwner && actor?.type === 'character';
|
||||
}
|
||||
|
||||
static getRollType(parent) {
|
||||
return 'trait';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,12 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
max: new fields.NumberField({ integer: true, label: 'DAGGERHEART.GENERAL.max' })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
targetDispositions: new fields.SetField(
|
||||
new fields.NumberField({
|
||||
choices: CONFIG.DH.GENERAL.simpleDispositions
|
||||
}),
|
||||
{ label: 'DAGGERHEART.ACTIVEEFFECT.Config.targetDispositions' }
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
@ -131,13 +137,14 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
|
|||
return armorChange.getArmorData();
|
||||
}
|
||||
|
||||
static getDefaultObject() {
|
||||
static getDefaultObject(options = { transfer: true }) {
|
||||
return {
|
||||
name: 'New Effect',
|
||||
id: foundry.utils.randomID(),
|
||||
disabled: false,
|
||||
img: 'icons/magic/life/heart-cross-blue.webp',
|
||||
description: '',
|
||||
transfer: options.transfer,
|
||||
statuses: [],
|
||||
changes: [],
|
||||
system: {
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class DhCharacter extends DhCreature {
|
|||
settingSheet: DHCharacterSettings,
|
||||
isNPC: false,
|
||||
hasInventory: true,
|
||||
quantifiable: ["loot", "consumable"]
|
||||
quantifiable: ['loot', 'consumable']
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -470,7 +470,7 @@ export default class DhCharacter extends DhCreature {
|
|||
|
||||
/* All items are valid on characters */
|
||||
isItemValid() {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class DhParty extends BaseDataActor {
|
|||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
hasInventory: true,
|
||||
quantifiable: ["weapon", "armor", "loot", "consumable"]
|
||||
quantifiable: ['weapon', 'armor', 'loot', 'consumable']
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,26 +7,31 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel {
|
|||
img: new fields.StringField({}),
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.StringField({}),
|
||||
actions: new fields.ArrayField(
|
||||
new fields.ObjectField({
|
||||
name: new fields.StringField({}),
|
||||
damage: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.StringField({})
|
||||
}),
|
||||
healing: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.StringField({})
|
||||
}),
|
||||
cost: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.NumberField({})
|
||||
}),
|
||||
target: new fields.SchemaField({
|
||||
type: new fields.StringField({ nullable: true })
|
||||
})
|
||||
})
|
||||
)
|
||||
source: new fields.SchemaField({
|
||||
actor: new fields.StringField(),
|
||||
item: new fields.StringField(),
|
||||
action: new fields.StringField()
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
get actionActor() {
|
||||
if (!this.source.actor) return null;
|
||||
return fromUuidSync(this.source.actor);
|
||||
}
|
||||
|
||||
get actionItem() {
|
||||
const actionActor = this.actionActor;
|
||||
if (!actionActor || !this.source.item) return null;
|
||||
|
||||
const item = actionActor.items.get(this.source.item);
|
||||
return item ? item.system.actions?.find(a => a.id === this.source.action) : null;
|
||||
}
|
||||
|
||||
get action() {
|
||||
const { actionItem: itemAction } = this;
|
||||
if (!this.source.action) return null;
|
||||
if (itemAction) return itemAction;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as AreasField } from './areasField.mjs';
|
||||
export { default as CostField } from './costField.mjs';
|
||||
export { default as CountdownField } from './countdownField.mjs';
|
||||
export { default as UsesField } from './usesField.mjs';
|
||||
|
|
|
|||
40
module/data/fields/action/areasField.mjs
Normal file
40
module/data/fields/action/areasField.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class AreasField extends fields.ArrayField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 150;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const element = new fields.SchemaField({
|
||||
name: new fields.StringField({
|
||||
nullable: false,
|
||||
initial: 'Area',
|
||||
label: 'DAGGERHEART.GENERAL.name'
|
||||
}),
|
||||
type: new fields.StringField({
|
||||
nullable: false,
|
||||
choices: CONFIG.DH.ACTIONS.areaTypes,
|
||||
initial: CONFIG.DH.ACTIONS.areaTypes.placed.id,
|
||||
label: 'DAGGERHEART.GENERAL.type'
|
||||
}),
|
||||
shape: new fields.StringField({
|
||||
nullable: false,
|
||||
choices: CONFIG.DH.GENERAL.templateTypes,
|
||||
initial: CONFIG.DH.GENERAL.templateTypes.circle.id,
|
||||
label: 'DAGGERHEART.ACTIONS.Config.area.shape'
|
||||
}),
|
||||
/* Could be opened up to allow numbers to be input aswell. Probably best handled via an autocomplete in that case to allow the select options but also free text */
|
||||
size: new fields.StringField({
|
||||
nullable: false,
|
||||
choices: CONFIG.DH.GENERAL.range,
|
||||
initial: CONFIG.DH.GENERAL.range.veryClose.id,
|
||||
label: 'DAGGERHEART.ACTIONS.Config.area.size'
|
||||
}),
|
||||
effects: new fields.ArrayField(new fields.DocumentIdField())
|
||||
});
|
||||
super(element, options, context);
|
||||
}
|
||||
}
|
||||
|
|
@ -40,9 +40,7 @@ export default class DHSummonField extends fields.ArrayField {
|
|||
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
|
||||
await roll.evaluate();
|
||||
const count = roll.total;
|
||||
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active)
|
||||
rolls.push(roll);
|
||||
|
||||
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll);
|
||||
|
||||
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
|
||||
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
|
||||
|
|
|
|||
|
|
@ -281,8 +281,14 @@ export function ActionMixin(Base) {
|
|||
name: this.name,
|
||||
img: this.baseAction ? this.parent.parent.img : this.img,
|
||||
tags: this.tags ? this.tags : ['Spell', 'Arcana', 'Lv 10'],
|
||||
areas: this.areas,
|
||||
summon: this.summon
|
||||
},
|
||||
source: {
|
||||
actor: this.actor.uuid,
|
||||
item: this.item.id,
|
||||
action: this.id
|
||||
},
|
||||
itemOrigin: this.item,
|
||||
description: this.description || (this.item instanceof Item ? this.item.system.description : '')
|
||||
};
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
get actionsList() {
|
||||
// No actions on non-characters
|
||||
if (this.metadata.isInventoryItem && this.actor && this.actor.type !== 'character') {
|
||||
return [];
|
||||
}
|
||||
return this.actions;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -99,7 +99,9 @@ export default class DHWeapon extends AttachableItem {
|
|||
/* -------------------------------------------- */
|
||||
|
||||
get actionsList() {
|
||||
return [this.attack, ...this.actions];
|
||||
// No actions on non-characters
|
||||
if (this.actor && this.actor.type !== 'character') return [];
|
||||
return [this.attack, ...super.actionsList];
|
||||
}
|
||||
|
||||
get customActions() {
|
||||
|
|
|
|||
1
module/data/regionBehavior/_module.mjs
Normal file
1
module/data/regionBehavior/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as applyActiveEffect } from './applyActiveEffect.mjs';
|
||||
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
40
module/data/regionBehavior/applyActiveEffect.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect {
|
||||
static async #getApplicableEffects(token) {
|
||||
const effects = await Promise.all(this.effects.map(foundry.utils.fromUuid));
|
||||
return effects.filter(
|
||||
effect => !effect.system.targetDispositions.size || effect.system.targetDispositions.has(token.disposition)
|
||||
);
|
||||
}
|
||||
|
||||
static async #onTokenEnter(event) {
|
||||
if (!event.user.isSelf) return;
|
||||
const { token, movement } = event.data;
|
||||
const actor = token.actor;
|
||||
if (!actor) return;
|
||||
const resumeMovement = movement ? token.pauseMovement() : undefined;
|
||||
const effects = await DhApplyActiveEffect.#getApplicableEffects.bind(this)(event.data.token);
|
||||
const toCreate = [];
|
||||
for (const effect of effects) {
|
||||
const data = effect.toObject();
|
||||
delete data._id;
|
||||
if (effect.compendium) {
|
||||
data._stats.duplicateSource = null;
|
||||
data._stats.compendiumSource = effect.uuid;
|
||||
} else {
|
||||
data._stats.duplicateSource = effect.uuid;
|
||||
data._stats.compendiumSource = null;
|
||||
}
|
||||
data._stats.exportSource = null;
|
||||
data.origin = this.parent.uuid;
|
||||
toCreate.push(data);
|
||||
}
|
||||
if (toCreate.length) await actor.createEmbeddedDocuments('ActiveEffect', toCreate);
|
||||
await resumeMovement?.();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
static events = {
|
||||
...CONFIG.RegionBehavior.dataModels.applyActiveEffect.events,
|
||||
[CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter
|
||||
};
|
||||
}
|
||||
|
|
@ -145,6 +145,7 @@ export default class DHRoll extends Roll {
|
|||
roll: this,
|
||||
parent: chatData.parent,
|
||||
targetMode: chatData.targetMode,
|
||||
areas: chatData.action?.areas,
|
||||
metagamingSettings
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
|
|||
static effectSafeEval(expression) {
|
||||
let result;
|
||||
try {
|
||||
// eslint-disable-next-line no-new-func
|
||||
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
|
||||
result = evl(Roll.MATH_PROXY);
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -602,7 +602,7 @@ export default class DhpActor extends Actor {
|
|||
rollData.system = this.system.getRollData();
|
||||
rollData.prof = this.system.proficiency ?? 1;
|
||||
rollData.cast = this.system.spellcastModifier ?? 1;
|
||||
|
||||
|
||||
return rollData;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
element.addEventListener('click', this.onApplyEffect.bind(this))
|
||||
);
|
||||
|
||||
for (const element of html.querySelectorAll('.action-areas')) {
|
||||
element.addEventListener('click', this.onCreateAreas.bind(this));
|
||||
}
|
||||
|
||||
html.querySelectorAll('.roll-target').forEach(element => {
|
||||
element.addEventListener('mouseenter', this.hoverTarget);
|
||||
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||
|
|
@ -249,6 +253,54 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
this.system.action?.workflow.get('effects')?.execute(config, targets, true);
|
||||
}
|
||||
|
||||
async onCreateAreas(event) {
|
||||
const createArea = async selectedArea => {
|
||||
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
|
||||
const { shape: type, size: range } = selectedArea;
|
||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
|
||||
|
||||
await canvas.regions.placeRegion(
|
||||
{
|
||||
name: selectedArea.name,
|
||||
shapes: [shapeData],
|
||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||
behaviors: [
|
||||
{
|
||||
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
|
||||
type: 'applyActiveEffect',
|
||||
system: {
|
||||
effects: effects
|
||||
}
|
||||
}
|
||||
],
|
||||
displayMeasurements: true,
|
||||
locked: false,
|
||||
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
|
||||
visibility: CONST.REGION_VISIBILITY.ALWAYS
|
||||
},
|
||||
{ create: true }
|
||||
);
|
||||
};
|
||||
|
||||
if (this.system.action.areas.length === 1) createArea(this.system.action.areas[0]);
|
||||
else if (this.system.action.areas.length > 1) {
|
||||
new foundry.applications.ux.ContextMenu.implementation(
|
||||
event.target,
|
||||
'.action-areas',
|
||||
this.system.action.areas.map(area => ({
|
||||
label: area.name,
|
||||
onClick: () => createArea(area)
|
||||
})),
|
||||
{
|
||||
jQuery: false,
|
||||
fixed: true
|
||||
}
|
||||
);
|
||||
|
||||
CONFIG.ux.ContextMenu.triggerContextMenu(event, '.action-areas');
|
||||
}
|
||||
}
|
||||
|
||||
filterPermTargets(targets) {
|
||||
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, 'update'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export default class DhActorCollection extends foundry.documents.collections.Act
|
|||
get party() {
|
||||
const id = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty);
|
||||
const actor = game.actors.get(id);
|
||||
return actor?.type === "party" ? actor : null;
|
||||
return actor?.type === 'party' ? actor : null;
|
||||
}
|
||||
|
||||
/** Ensure companions are initialized after all other subtypes. */
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ export default class DHItem extends foundry.documents.Item {
|
|||
return this.system.metadata.isInventoryItem ?? false;
|
||||
}
|
||||
|
||||
/** Returns true if the item can be used */
|
||||
get usable() {
|
||||
const actor = this.actor;
|
||||
const actionsList = this.system.actionsList;
|
||||
return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length);
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
static async createDialog(data = {}, createOptions = {}, options = {}) {
|
||||
const { folders, types, template, context = {}, ...dialogOptions } = options;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function DhTemplateEnricher(match, _options) {
|
|||
)?.id
|
||||
: params.range;
|
||||
|
||||
if (!Object.values(CONFIG.DH.GENERAL.templateTypes).find(x => x === type) || !range) return match[0];
|
||||
if (!CONFIG.DH.GENERAL.templateTypes[type] || !range) return match[0];
|
||||
|
||||
const label = game.i18n.localize(`DAGGERHEART.CONFIG.TemplateTypes.${type}`);
|
||||
const rangeDisplay = Number.isNaN(Number(range))
|
||||
|
|
@ -49,8 +49,6 @@ export default function DhTemplateEnricher(match, _options) {
|
|||
}
|
||||
|
||||
export const renderMeasuredTemplate = async event => {
|
||||
const { LINE, RECTANGLE, INFRONT, CONE } = CONFIG.DH.GENERAL.templateTypes;
|
||||
|
||||
const button = event.currentTarget,
|
||||
type = button.dataset.type,
|
||||
range = button.dataset.range,
|
||||
|
|
@ -59,49 +57,16 @@ export const renderMeasuredTemplate = async event => {
|
|||
|
||||
if (!type || !range || !game.canvas.scene) return;
|
||||
|
||||
const usedType = type === 'inFront' ? 'cone' : type;
|
||||
const usedAngle =
|
||||
type === CONE ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === INFRONT ? '180' : undefined;
|
||||
|
||||
let baseDistance = getTemplateDistance(range);
|
||||
|
||||
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
|
||||
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
|
||||
const sceneGridDistance = canvas.scene?.grid.distance ?? distance.getInitialValue();
|
||||
const dimensionConstant = sceneGridSize / sceneGridDistance;
|
||||
|
||||
baseDistance *= dimensionConstant;
|
||||
|
||||
const length = baseDistance;
|
||||
const radius = length;
|
||||
|
||||
const shapeWidth = type === LINE ? 5 * dimensionConstant : type === RECTANGLE ? length : undefined;
|
||||
|
||||
const { width, height } = game.canvas.scene.dimensions;
|
||||
const shapeData = {
|
||||
x: width / 2,
|
||||
y: height / 2,
|
||||
base: {
|
||||
type: 'token',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1,
|
||||
height: 1,
|
||||
shape: game.canvas.grid.isHexagonal ? CONST.TOKEN_SHAPES.ELLIPSE_1 : CONST.TOKEN_SHAPES.RECTANGLE_1
|
||||
},
|
||||
t: usedType,
|
||||
length: length,
|
||||
width: shapeWidth,
|
||||
height: length,
|
||||
angle: usedAngle,
|
||||
radius: radius,
|
||||
direction: direction,
|
||||
type: usedType
|
||||
};
|
||||
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({
|
||||
type,
|
||||
angle,
|
||||
range,
|
||||
direction
|
||||
});
|
||||
|
||||
await canvas.regions.placeRegion(
|
||||
{
|
||||
name: usedType.capitalize(),
|
||||
name: type.capitalize(),
|
||||
shapes: [shapeData],
|
||||
restriction: { enabled: false, type: 'move', priority: 0 },
|
||||
behaviors: [],
|
||||
|
|
@ -113,11 +78,3 @@ export const renderMeasuredTemplate = async event => {
|
|||
{ create: true }
|
||||
);
|
||||
};
|
||||
|
||||
const getTemplateDistance = range => {
|
||||
const rangeNumber = Number(range);
|
||||
if (!Number.isNaN(rangeNumber)) return rangeNumber;
|
||||
|
||||
const settings = canvas.scene?.rangeSettings;
|
||||
return settings ? settings[range] : 0;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||
'systems/daggerheart/templates/actionTypes/uses.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/roll.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/save.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/areas.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/cost.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/range-target.hbs',
|
||||
'systems/daggerheart/templates/actionTypes/effect.hbs',
|
||||
|
|
|
|||
|
|
@ -56,10 +56,10 @@ export const registerKeyBindings = () => {
|
|||
game.keybindings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.keybindings.partySheet, {
|
||||
name: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.name'),
|
||||
hint: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.hint'),
|
||||
editable: [{ key: "KeyP" }],
|
||||
editable: [{ key: 'KeyP' }],
|
||||
onDown: () => {
|
||||
const controlled = canvas.ready ? canvas.tokens.controlled : [];
|
||||
const selectedParty = controlled.find((c) => c.actor?.type === 'party')?.actor;
|
||||
const selectedParty = controlled.find(c => c.actor?.type === 'party')?.actor;
|
||||
const party = selectedParty ?? game.actors.party;
|
||||
if (!party) return;
|
||||
|
||||
|
|
@ -215,6 +215,6 @@ const registerNonConfigSettings = () => {
|
|||
scope: 'world',
|
||||
config: false,
|
||||
type: String,
|
||||
default: null,
|
||||
default: null
|
||||
});
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue