foundryvtt-reve-de-dragon/module/misc.js
Vincent Vandemeulebrouck 864194e3b4 Correction "lacks permission to update"
Correction de message d'erreur

'User ... lacks permission to update Item ... in parent Actor'

Causé par le traitement d'un hook onUpdateActor qui semble
autorisé à modifier l'actor, mais ne l'est pas
- lors de modifications/ajouts de blessures
- lors de l'ajout d'effets
2024-11-24 23:12:03 +01:00

292 lines
8.3 KiB
JavaScript

import { Grammar } from "./grammar.js";
/**
* This class is intended as a placeholder for utility methods unrelated
* to actual classes of the game system or of FoundryVTT
*/
export class Misc {
static isFunction(v) {
return v && {}.toString.call(v) === '[object Function]';
}
static upperFirst(text) {
return text.charAt(0).toUpperCase() + text.slice(1);
}
static lowerFirst(text) {
return text.charAt(0).toLowerCase() + text.slice(1);
}
static toSignedString(number) {
const value = parseInt(number)
const isPositiveNumber = value != NaN && value > 0;
return isPositiveNumber ? "+" + number : number
}
static modulo(n, m) {
return ((n % m) + m) % m;
}
static sum() {
return (a, b) => Number(a) + Number(b);
}
static ascending(orderFunction = x => x) {
return (a, b) => Misc.sortingBy(orderFunction(a), orderFunction(b));
}
static descending(orderFunction = x => x) {
return (a, b) => Misc.sortingBy(orderFunction(b), orderFunction(a));
}
static sortingBy(a, b) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
}
static typeName(type, subType) {
return subType ? game.i18n.localize(`TYPES.${type}.${subType}`)
: '';
}
static arrayOrEmpty(items) {
return items?.length ? items : [];
}
/**
* Converts the value to an integer, or to 0 if undefined/null/not representing integer
* @param {*} value value to convert to an integer using parseInt
*/
static toInt(value) {
if (value == undefined) {
return 0;
}
const parsed = parseInt(value);
return isNaN(parsed) ? 0 : parsed;
}
static keepDecimals(num, decimals) {
if (decimals <= 0 || decimals > 6) return num;
const power10n = Math.pow(10, parseInt(decimals));
return Math.round(num * power10n) / power10n;
}
static getFractionHtml(diviseur) {
if (!diviseur || diviseur <= 1) return undefined;
switch (diviseur || 1) {
case 2: return '&frac12;';
case 4: return '&frac14;';
default: return '1/' + diviseur;
}
}
static indexLowercase(list) {
const obj = {};
const addToObj = (map, val) => {
const key = Grammar.toLowerCaseNoAccent(val);
if (key && !map[key]) map[key] = val
}
list.forEach(it => addToObj(obj, it))
return obj;
}
static concat(lists) {
return lists.reduce((a, b) => a.concat(b), []);
}
static classify(items, classifier = it => it.type) {
let itemsBy = {}
Misc.classifyInto(itemsBy, items, classifier)
return itemsBy
}
static classifyFirst(items, classifier) {
let itemsBy = {};
for (const item of items) {
const classification = classifier(item);
if (!itemsBy[classification]) {
itemsBy[classification] = item;
}
}
return itemsBy;
}
static classifyInto(itemsBy, items, classifier = it => it.type) {
for (const item of items) {
const classification = classifier(item)
let list = itemsBy[classification];
if (!list) {
list = []
itemsBy[classification] = list
}
list.push(item)
}
}
/**
* @returns an array of incremental integers (including from / excluding to).
* if max<min, the array is decrementing integers
*/
static intArray(from, to) {
if (from > to) {
return Array.from(Array(from - to).keys()).map(i => from - i)
}
return Array.from(Array(to - from).keys()).map(i => from + i)
}
static distinct(array) {
return [...new Set(array)];
}
static join(params, separator = '') {
return (!params || params.length == 0) ? '' : params.reduce(Misc.joining(separator))
}
static joining(separator = '') {
return (a, b) => a + separator + b;
}
static connectedGMOrUser(ownerId = undefined) {
if (ownerId && game.user.id == ownerId) {
return ownerId;
}
return Misc.firstConnectedGM()?.id ?? game.user.id;
}
static isRollModeHiddenToPlayer() {
switch (game.settings.get("core", "rollMode")) {
case CONST.DICE_ROLL_MODES.BLIND:
case CONST.DICE_ROLL_MODES.SELF: return true;
}
return false
}
static getActiveUser(id) {
return game.users.find(u => u.id == id && u.active);
}
static firstConnectedGM() {
if (foundry.utils.isNewerVersion(game.release.version, '12.0')) {
return game.users.activeGM
}
return game.users.find(u => u.isGM && u.active);
}
static connectedGMs() {
return game.users.filter(u => u.isGM && u.active);
}
/**
* This helper method allows to get the docuument, for a single user (either first connected GM, or the owner
* if there is no connected GMs), or else return undefined.
*
* This allows for example update hooks that should apply modifications to actors to be called only for one
* user (preventing the "User ... lacks permission to update Item" that was occuring on hooks when Item updates
* were triggering other changes)
*
* @param {*} document the Document with is potentially an Actor
* @returns the actor if either the game.user is the first connected GM, or if the game.user is the owner
* and there is no connected GM
*/
static documentIfResponsible(document) {
if (Misc.isFirstConnectedGM() || (Misc.connectedGMs().length == 0 && Misc.isFirstOwnerPlayer(document))) {
return document
}
return undefined
}
static isOwnerPlayer(document) {
return document.testUserPermission && document.testUserPermission(game.user, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)
}
static isFirstOwnerPlayer(document) {
if (!document.testUserPermission){
return false
}
return game.users.filter(u => document.testUserPermission(u, CONST.DOCUMENT_OWNERSHIP_LEVELS.OWNER)) == game.user
}
static isOwnerPlayerOrUniqueConnectedGM(actor) {
return Misc.isFirstOwnerPlayer(actor) ?? Misc.isFirstConnectedGM();
}
/**
* @returns true pour un seul utilisateur: le premier GM connecté par ordre d'id
*/
static isFirstConnectedGM() {
return game.user == Misc.firstConnectedGM();
}
static firstConnectedGMId() {
return Misc.firstConnectedGM()?.id;
}
/* -------------------------------------------- */
static findPlayer(name) {
return Misc.findFirstLike(name, game.users, { description: 'joueur' });
}
/* -------------------------------------------- */
static findActor(name, actors = game.actors) {
return Misc.findFirstLike(name, actors, { description: 'acteur' });
}
/* -------------------------------------------- */
static findFirstLike(value, elements, options = {}) {
options = foundry.utils.mergeObject({
mapper: it => it.name,
preFilter: it => true,
description: 'valeur',
onMessage: m => ui.notifications.info(m)
}, options);
const subset = this.findAllLike(value, elements, options);
if (subset.length == 0) {
console.log(`Aucune ${options.description} pour ${value}`);
return undefined
}
if (subset.length == 1) {
return subset[0]
}
let single = subset.find(it => Grammar.toLowerCaseNoAccent(options.mapper(it)) == Grammar.toLowerCaseNoAccent(value));
if (!single) {
single = subset[0];
const choices = Misc.join(subset.map(it => options.mapper(it)), '<br>');
options.onMessage(`Plusieurs choix de ${options.description}s possibles:<br>${choices}<br>Le premier sera choisi: ${options.mapper(single)}`);
}
return single;
}
static findAllLike(value, elements, options = {}) {
options = foundry.utils.mergeObject({
mapper: it => it.name,
preFilter: it => true,
description: 'valeur',
onMessage: m => ui.notifications.info(m)
}, options);
if (!value) {
options.onMessage(`Pas de ${options.description} correspondant à une valeur vide`);
return [];
}
value = Grammar.toLowerCaseNoAccent(value);
const subset = elements.filter(options.preFilter)
.filter(it => Grammar.toLowerCaseNoAccent(options.mapper(it))?.includes(value));
if (subset.length == 0) {
options.onMessage(`Pas de ${options.description} correspondant à ${value}`);
}
return subset;
}
static cssRotation(angle) {
const rotation = `rotate(${angle}deg)`;
return {
'transform': rotation,
'-ms-transform': rotation,
'-moz-transform': rotation,
'-webkit-transform': rotation,
'-o-transform': rotation
};
}
}