Merge remote-tracking branch 'origin/main' into foundry/v12
This commit is contained in:
commit
76cca3f672
17 changed files with 223 additions and 81 deletions
7
module/documents/ActiveEffect/GenericActiveEffect.mjs
Normal file
7
module/documents/ActiveEffect/GenericActiveEffect.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export class DotDungeonActiveEffect extends ActiveEffect {
|
||||
|
||||
// Invert the logic of the disabled property so it's easier to modify via
|
||||
// embedded controls
|
||||
get enabled() { return !this.disabled };
|
||||
set enabled(newValue) { this.disabled = !newValue };
|
||||
};
|
||||
42
module/documents/ActiveEffect/_proxy.mjs
Normal file
42
module/documents/ActiveEffect/_proxy.mjs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { DotDungeonActiveEffect } from "./GenericActiveEffect.mjs";
|
||||
|
||||
const classes = {};
|
||||
|
||||
const defaultClass = DotDungeonActiveEffect;
|
||||
|
||||
export const ActiveEffectProxy = new Proxy(function () {}, {
|
||||
construct(target, args) {
|
||||
const [data] = args;
|
||||
|
||||
if (!classes.hasOwnProperty(data.type)) {
|
||||
return new defaultClass(...args);
|
||||
}
|
||||
|
||||
return new classes[data.type](...args);
|
||||
},
|
||||
get(target, prop, receiver) {
|
||||
|
||||
if (["create", "createDocuments"].includes(prop)) {
|
||||
return function (data, options) {
|
||||
if (data.constructor === Array) {
|
||||
return data.map(i => ActiveEffectProxy.create(i, options))
|
||||
}
|
||||
|
||||
if (!classes.hasOwnProperty(data.type)) {
|
||||
return defaultClass.create(data, options);
|
||||
}
|
||||
|
||||
return classes[data.type].create(data, options);
|
||||
};
|
||||
};
|
||||
|
||||
if (prop == Symbol.hasInstance) {
|
||||
return function (instance) {
|
||||
if (instance instanceof defaultClass) return true;
|
||||
return Object.values(classes).some(i => instance instanceof i);
|
||||
};
|
||||
};
|
||||
|
||||
return defaultClass[prop];
|
||||
},
|
||||
});
|
||||
|
|
@ -1,4 +1,15 @@
|
|||
export class DotDungeonActor extends Actor {
|
||||
|
||||
/*
|
||||
Using this to take a "snapshot" of the system data prior to applying AE's so
|
||||
that the inputs can still have the non-modified value in them, while we still
|
||||
provide all that data to AE's without needing to disable any inputs.
|
||||
*/
|
||||
prepareEmbeddedDocuments() {
|
||||
this.preAE = foundry.utils.deepClone(this.system);
|
||||
super.prepareEmbeddedDocuments();
|
||||
};
|
||||
|
||||
async createEmbeddedItem(defaults, opts = {}) {
|
||||
let items = await this.createEmbeddedDocuments(`Item`, defaults);
|
||||
if (!Array.isArray(items)) items = items ? [items] : [];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,22 @@
|
|||
import { DotDungeonActor } from "./GenericActor.mjs";
|
||||
import { DotDungeonItem } from "../Item/GenericItem.mjs";
|
||||
|
||||
export class Player extends DotDungeonActor {
|
||||
|
||||
applyActiveEffects() {
|
||||
super.applyActiveEffects();
|
||||
|
||||
/*
|
||||
These are the (groups of) fields that ActiveEffects may modify safely and
|
||||
remain editable in the sheet. This needs to be done because of default
|
||||
Foundry behaviour that otherwise prevents these fields from being edited.
|
||||
The deletes must use optional chaining otherwise they can cause issues
|
||||
during the document preparation lifecycle as an actor with no AE's affecting
|
||||
anything in one of these areas will result in these paths being undefined.
|
||||
*/
|
||||
delete this.overrides.system?.stats;
|
||||
delete this.overrides.system?.skills;
|
||||
};
|
||||
|
||||
async createCustomPet() {
|
||||
const body = new URLSearchParams({
|
||||
number: 1,
|
||||
|
|
|
|||
|
|
@ -5,4 +5,11 @@ export class Material extends DotDungeonItem {
|
|||
let affects = game.settings.get(`dotdungeon`, `materialsAffectCapacity`);
|
||||
return affects ? super.usedCapacity : 0;
|
||||
};
|
||||
|
||||
get availableLocations() {
|
||||
return [
|
||||
{ value: null, label: `dotdungeon.location.unknown` },
|
||||
{ value: `inventory`, label: `dotdungeon.location.inventory` },
|
||||
];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { SyncData } from "./models/Actor/Sync.mjs";
|
|||
import { MobData } from "./models/Actor/Mob.mjs";
|
||||
|
||||
// Main Documents
|
||||
import { ActiveEffectProxy } from "./documents/ActiveEffect/_proxy.mjs";
|
||||
import { ActorProxy } from "./documents/Actor/_proxy.mjs";
|
||||
import { ItemProxy } from "./documents/Item/_proxy.mjs";
|
||||
|
||||
|
|
@ -56,6 +57,7 @@ Hooks.once(`init`, async () => {
|
|||
CONFIG.Item.dataModels.pet = PetItemData;
|
||||
CONFIG.Actor.documentClass = ActorProxy;
|
||||
CONFIG.Item.documentClass = ItemProxy;
|
||||
CONFIG.ActiveEffect.documentClass = ActiveEffectProxy;
|
||||
|
||||
CONFIG.DOTDUNGEON = DOTDUNGEON;
|
||||
|
||||
|
|
@ -111,9 +113,6 @@ Hooks.once(`init`, async () => {
|
|||
hbs.registerHandlebarsHelpers();
|
||||
hbs.preloadHandlebarsTemplates();
|
||||
registerCustomComponents();
|
||||
|
||||
CONFIG.CACHE ??= {};
|
||||
CONFIG.CACHE.icons = await hbs.preloadIcons();
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,25 +43,6 @@ export const preAliasedPartials = {
|
|||
"dotdungeon.pc.v2.foil": "actors/char-sheet/v2/partials/inventory/items/untyped.v2.pc.hbs",
|
||||
};
|
||||
|
||||
export const icons = [
|
||||
`caret-right.svg`,
|
||||
`caret-down.svg`,
|
||||
`garbage-bin.svg`,
|
||||
`chat-bubble.svg`,
|
||||
`dice/d4.svg`,
|
||||
`dice/d6.svg`,
|
||||
`dice/d8.svg`,
|
||||
`dice/d10.svg`,
|
||||
`dice/d12.svg`,
|
||||
`dice/d20.svg`,
|
||||
`create.svg`,
|
||||
`close.svg`,
|
||||
`edit.svg`,
|
||||
`sheet.svg`,
|
||||
`minus.svg`,
|
||||
];
|
||||
|
||||
|
||||
export async function registerHandlebarsHelpers() {
|
||||
Handlebars.registerHelper(helpers);
|
||||
};
|
||||
|
|
@ -97,38 +78,3 @@ export async function preloadHandlebarsTemplates() {
|
|||
console.groupEnd();
|
||||
return loadTemplates(paths);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads all of the icons that are needed in the handlebars templating to make
|
||||
* the sheet look nicer.
|
||||
*
|
||||
* @returns An object containing icon names to the corresponding HTML data for
|
||||
* displaying the icon
|
||||
*/
|
||||
export async function preloadIcons() {
|
||||
const pathPrefix = `systems/dotdungeon/assets/`
|
||||
const parsedIcons = {};
|
||||
|
||||
for (const icon of icons) {
|
||||
const iconName = icon.split(`/`).slice(-1)[0].slice(0, -4);
|
||||
if (icon.endsWith(`.svg`)) {
|
||||
try {
|
||||
const response = await fetchWithTimeout(`${pathPrefix}${icon}`);
|
||||
if (response.status !== 200) { continue };
|
||||
const svgData = await response.text();
|
||||
parsedIcons[iconName] = svgData;
|
||||
} catch {
|
||||
console.error(`.dungeon | Failed to fetch/parse icon: ${icon}`);
|
||||
continue;
|
||||
};
|
||||
}
|
||||
else if (icon.endsWith(`.png`)) {
|
||||
parsedIcons[iconName] = `<img alt="" src="${pathPrefix}${icon}">`;
|
||||
}
|
||||
else {
|
||||
console.warn(`.dungeon | Icon "${icon}" failed to be handled by a loader`)
|
||||
};
|
||||
};
|
||||
|
||||
return parsedIcons;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,9 +39,8 @@ export class PlayerSheetv2 extends GenericActorSheet {
|
|||
|
||||
html.find(`.create-ae`).on(`click`, async ($e) => {
|
||||
console.debug(`Creating an ActiveEffect?`);
|
||||
ActiveEffect.implementation.create({
|
||||
name: "Default AE",
|
||||
}, { parent: this.actor, renderSheet: true });
|
||||
const ae = this.actor.createEmbeddedDocuments(`ActiveEffect`, [{name: "Default AE"}]);
|
||||
ae.sheet.render(true);
|
||||
});
|
||||
html.find(`[data-filter-toggle]`).on(`change`, ($e) => {
|
||||
const target = $e.delegateTarget;
|
||||
|
|
@ -74,6 +73,7 @@ export class PlayerSheetv2 extends GenericActorSheet {
|
|||
/** @type {ActorHandler} */
|
||||
const actor = this.actor;
|
||||
|
||||
ctx.preAE = actor.preAE;
|
||||
ctx.system = actor.system;
|
||||
ctx.flags = actor.flags;
|
||||
ctx.items = this.actor.itemTypes;
|
||||
|
|
@ -97,6 +97,7 @@ export class PlayerSheetv2 extends GenericActorSheet {
|
|||
const stat = {
|
||||
key: statName,
|
||||
name: localizer(`dotdungeon.stat.${statName}`),
|
||||
original: this.actor.preAE.stats[statName],
|
||||
value: this.actor.system.stats[statName],
|
||||
};
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ export class PlayerSheetv2 extends GenericActorSheet {
|
|||
return {
|
||||
value: die,
|
||||
label: localizer(`dotdungeon.die.${die}`, { stat: statName }),
|
||||
disabled: usedDice.has(die) && this.actor.system.stats[statName] !== die,
|
||||
disabled: usedDice.has(die) && this.actor.preAE.stats[statName] !== die,
|
||||
};
|
||||
})
|
||||
];
|
||||
|
|
@ -127,8 +128,9 @@ export class PlayerSheetv2 extends GenericActorSheet {
|
|||
key: skill,
|
||||
name: game.i18n.format(`dotdungeon.skills.${skill}`),
|
||||
value,
|
||||
original: this.actor.preAE.skills[statName][skill],
|
||||
formula: `1` + stat.value + modifierToString(value, { spaces: true }),
|
||||
rollDisabled: value === -1,
|
||||
rollDisabled: this.actor.preAE.skills[statName][skill] === -1,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export class GenericActorSheet extends ActorSheet {
|
|||
|
||||
ctx.actor = this.actor;
|
||||
ctx.config = DOTDUNGEON;
|
||||
ctx.icons = CONFIG.CACHE.icons;
|
||||
ctx.icons = {};
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -30,9 +30,10 @@ export class GenericItemSheet extends ItemSheet {
|
|||
ctx.item = this.item;
|
||||
ctx.system = this.item.system;
|
||||
ctx.flags = this.item.flags;
|
||||
ctx.effects = this.item.effects;
|
||||
|
||||
ctx.config = DOTDUNGEON;
|
||||
ctx.icons = CONFIG.CACHE.icons;
|
||||
ctx.icons = {};
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { GenericContextMenu } from "../../utils/GenericContextMenu.mjs";
|
||||
import { DialogManager } from "../../utils/DialogManager.mjs";
|
||||
import { GenericItemSheet } from "./GenericItemSheet.mjs";
|
||||
import { localizer } from "../../utils/localizer.mjs";
|
||||
|
||||
export class UntypedItemSheet extends GenericItemSheet {
|
||||
static get defaultOptions() {
|
||||
|
|
@ -28,22 +30,78 @@ export class UntypedItemSheet extends GenericItemSheet {
|
|||
|
||||
new GenericContextMenu(html, `.photo.panel`, [
|
||||
{
|
||||
name: `View Larger`,
|
||||
callback: (html) => {
|
||||
console.log(`.dungeon | View Larger`);
|
||||
name: localizer(`dotdungeon.common.view-larger`),
|
||||
callback: () => {
|
||||
(new ImagePopout(this.item.img)).render(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: `Change Photo`,
|
||||
name: localizer(`dotdungeon.common.edit`),
|
||||
condition: () => this.isEditable,
|
||||
callback: (html) => {
|
||||
console.log(`.dungeon | Change Photo`);
|
||||
callback: () => {
|
||||
const fp = new FilePicker({
|
||||
callback: (path) => {
|
||||
this.item.update({"img": path});
|
||||
},
|
||||
});
|
||||
fp.render(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: localizer(`dotdungeon.common.reset`),
|
||||
condition: () => this.isEditable,
|
||||
callback: () => {
|
||||
console.log(`.dungeon | Reset Item Image`)
|
||||
},
|
||||
}
|
||||
]);
|
||||
|
||||
if (!this.isEditable) return;
|
||||
console.debug(`.dungeon | Adding event listeners for Untyped Item: ${this.item.id}`);
|
||||
|
||||
html.find(`.create-ae`).on(`click`, async () => {
|
||||
await this.item.createEmbeddedDocuments(
|
||||
`ActiveEffect`,
|
||||
[{name: localizer(`dotdungeon.default.name`, { document: `ActiveEffect`, type: `base` })}],
|
||||
{ renderSheet: true }
|
||||
);
|
||||
});
|
||||
|
||||
new GenericContextMenu(html, `.effect.panel`, [
|
||||
{
|
||||
name: localizer(`dotdungeon.common.edit`),
|
||||
callback: async (html) => {
|
||||
(await fromUuid(html.closest(`.effect`)[0].dataset.embeddedId))?.sheet.render(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: localizer(`dotdungeon.common.delete`),
|
||||
callback: async (html) => {
|
||||
const target = html.closest(`.effect`)[0];
|
||||
const data = target.dataset;
|
||||
const id = data.embeddedId;
|
||||
const doc = await fromUuid(id);
|
||||
DialogManager.createOrFocus(
|
||||
`${doc.uuid}-delete`,
|
||||
{
|
||||
title: localizer(`dotdungeon.delete.ActiveEffect.title`, doc),
|
||||
content: localizer(`dotdungeon.delete.ActiveEffect.content`, doc),
|
||||
buttons: {
|
||||
yes: {
|
||||
label: localizer(`Yes`),
|
||||
callback() {
|
||||
doc.delete();
|
||||
},
|
||||
},
|
||||
no: {
|
||||
label: localizer(`No`),
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
async getData() {
|
||||
|
|
|
|||
|
|
@ -18,12 +18,20 @@ export function localizer(key, args = {}, depth = 0) {
|
|||
return localized;
|
||||
};
|
||||
|
||||
/*
|
||||
Helps prevent recursion on the same key so that we aren't doing excess work.
|
||||
*/
|
||||
const localizedSubkeys = new Map();
|
||||
for (const match of subkeys) {
|
||||
const subkey = match.groups.key;
|
||||
localized =
|
||||
localized.slice(0, match.index)
|
||||
+ localizer(subkey, args, depth + 1)
|
||||
+ localized.slice(match.index + subkey.length + 1)
|
||||
if (localizedSubkeys.has(subkey)) continue;
|
||||
localizedSubkeys.set(subkey, localizer(subkey, args, depth + 1));
|
||||
};
|
||||
return localized;
|
||||
|
||||
return localized.replace(
|
||||
localizerConfig.subKeyPattern,
|
||||
(_fullMatch, subkey) => {
|
||||
return localizedSubkeys.get(subkey);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue