Modèle de module révélateur
Le modèle de modulereproduit le concept des classes d'autres langages en encapsulant les membres privés et publics dans un objet. Le modèle de module révélateur améliore le modèle de module en rendant la syntaxe plus cohérente.
var myRevealingModule = myRevealingModule || (function() {
var privateVar = 'Cette variable est privée',
publicVar = 'Cette variable est publique';
function privateFunction() {
log(privateVar);
}
function publicSet(text) {
privateVar = text;
}
function publicGet() {
privateFunction();
}
return {
setFunc: publicSet,
myVar: publicVar,
getFunc: publicGet
};
}());
log(myRevealingModule.getFunc()); // « Cette variable est privée »
myRevealingModule.setFunc('Cependant, je peux modifier sa valeur');
log(myRevealingModule.getFunc()); // « Cependant, je peux modifier sa valeur »
log(myRevealingModule.myVar); // « Cette variable est publique »
myRevealingModule.myVar = 'Je peux donc la modifier à ma convenance' ;
log(myRevealingModule.myVar) ; // « Je peux donc la modifier à ma convenance »
Mémorisation
La mémorisation est une technique d'optimisation qui consiste à stocker le résultat d'une entrée donnée, permettant ainsi d'obtenir le même résultat sans avoir à le calculer une seconde fois. Ceci est particulièrement utile dans le cadre de calculs complexes. Bien entendu, si votre fonction reçoit rarement la même entrée, la mémorisation sera un outil limité, tandis que les besoins en stockage continueront d'augmenter.
var factorialCache = {};
function factorial(n) {
var x;
n = parseInt(n || 0);
if (n < 0) {
throw 'Les factoriels des nombres négatifs ne sont pas bien définis';
}
if (n === 0) {
return 1;
}else if (factorialCache[n]) {
return factorialCache[n];
}
x = factorial(n - 1) * n;
factorialCache[n] = x;
return x;
}
Dans un script Roll20 Mod (API), les valeurs mises en cache pourraient être stockées dans l'état, qui persistera entre les sessions de jeu. Si vous disposez d'un grand nombre d'entrées potentielles, veuillez toutefois noter que Roll20 peut limiter votre utilisation del'état.
Sémaphore asynchrone
Un sémaphore asynchrone vous permet de déclencher une méthode de rappel une fois qu'un ensemble d'opérations asynchrones (telles que les appels à sendChat) a été exécuté. Bien qu'il ne soit pas possible de garantirl'ordredans lequel les opérations seront exécutées, il est possible de garantir qu'ellesseronttoutes terminées lorsque le rappel du sémaphore se déclenchera.
Lorsque vous utilisez un sémaphore, veuillez appelerv()avant d'appeler chaque opération asynchrone, et appelerp()comme dernière instruction de chaque opération asynchrone. Si le nombre d'opérations que vous allez effectuer est connu à l'avance, vous pouvez également fournir ce nombre au constructeur du sémaphore et omettre les appels àv.
Cette implémentation particulière d'un sémaphore asynchrone vous permet également de fournir un contexte pour la fonction de rappel (définir la valeur dethis), ainsi que de transmettre des paramètres à la fonction de rappel. Les paramètres peuvent être fournis soit dans le constructeur, soit dans l'appel àp. (Les paramètres danspont priorité sur les paramètres dans le constructeur.)
function Semaphore(callback, initial, context) {
var args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
this.lock = parseInt(initial, 10) || 0;
this.callback = callback;
this.context = context || callback;
this.args = args.slice(3);
}
Semaphore.prototype = {
v: function() { this.lock++; },
p: function() {
var parameters;
this.lock--;
if (this.lock === 0 && this.callback) {
// autoriser sem.p(arg1, arg2, ...) à remplacer les arguments passés au constructeur Semaphore
if (arguments.length > 0) { parameters = arguments; }
else { parameters = this.args; }
this.callback.apply(this.context, parameters);
}
}
};
Exemple d'utilisation :
var sem = new Semaphore(function(lastAsync) {
log(lastAsync + ' terminé en dernier');
log(this);
}, 2, { foo: 'bar', fizz: 'buzz' }, 'Sir n'apparaît pas dans ce rappel');
sendChat('', '/roll d20', function(ops) {
log('Exécution du premier sendChat');
sem.p('Premier appel sendChat');
});
sendChat('', '/roll d20', function(ops) {
log('Exécution du deuxième sendChat');
sem.p('Deuxième appel sendChat');
});
Exemple de résultat :
« Exécution du deuxième sendChat »
« Exécution du premier sendChat »
« Premier appel sendChat terminé en dernier »
{ foo : « bar », fizz : « buzz » }
Documents & Personnages
Création d'un document à distribuer
En raison de la manière dont les blocs de texte Document sont gérés, la création d'un objet Document doit se faire en deux étapes : commencez par créer l'objet, puis définissez les blocs de texte :
//Créer un nouveau Document accessible à tous les joueurs
var handout = createObj("handout", {
name: "Nom du document",
inplayerjournals: "all",
archived: false
});
handout.set('notes', 'Les notes doivent être définies après la création du document.');
handout.set('gmnotes', 'Les notes du MJ doivent également être définies après la création du document.');
Gestion de l'encodage
Les blocs de texte dans les documents (notes et Notes du MJ) et les personnages (biographie et Notes du MJ) qui sont définis via l'interface utilisateur sont stockés au formatx-www-form-urlencoded. Vous pouvez le reconnaître grâce à la séquence de codes %## dans tout le texte :
« Erik%20%28Viking%2BScientifique%29%20%5BCombattant%3A%203%2C%20Sorcier%3A%202%5D»
Ce texte peut être envoyé dans le chat et sera traduit par le navigateur. Toutefois, si vous devez apporter des modifications au texte, il est préférable de le traiter tel qu'il a été saisi :
Erik (Viking + Scientifique) [Combattant : 3, Sorcier : 2]
Vous pouvez décoder le texte encodé à l'aide de la fonction suivante :
var decodeUrlEncoding = function(t){
return t.replace(
/%([0-9A-Fa-f]{1,2})/g,
function(f,n){
return String.fromCharCode(parseInt(n,16));
}
);
}
Fonctions outils
Les fonctions outils accomplissent des tâches courantes que vous pourriez souhaiter utiliser dans de nombreux scripts. Si vous placez une fonction dans la portée la plus externe d'un onglet de script, cette fonction devrait être accessible à tous vos scripts, ce qui réduit votre charge de travail. Vous trouverez ci-dessous une sélection de ces fonctions.
décoder le texte de l'éditeur
Dépendances :Aucune
Le nouvel éditeur de texte intégré au jeu est très agréable, mais il pose un problème pour les scripts Mod (API) qui dépendent de la lecture d'informations provenant d'une des grandes zones de texte du jeu. Cette fonction facilite cette tâche.
À partir du texte de la propriétégmnotesd'un graphique, de la propriétébioougmnotesd'un personnage, ou de la propriéténotesougmnotesd'un document, cette fonction renvoie une version sans le formatage automatique de l'éditeur.
const decodeEditorText = (t, o) =>{
let w = t;
o = Object.assign({ separator: '\r\n', asArray: false },o);
/* Notes du MJ */
if(/^%3Cp%3E/.test(w)){
w = unescape(w);
} }
if(/^<p>/.test(w)){
let lines = w.match(/<p>.*?<\/p>/g)
.map( l => l.replace(/^<p>(.*?)<\/p>$/,'$1'));
return o.asArray ? lignes : lignes.join(o.separator) ;
}
/* ni l'un ni l'autre */
return t ;
};
Le premier argument est le texte à traiter.
const text = décoderLeTexteDeL'Éditeur(token.get('gmnotes'));
Par défaut, les lignes de texte seront séparées par \r\n.
Le deuxième argument facultatif est un objet contenant des options.
séparateur-- spécifie l'élément à utiliser pour séparer les lignes de texte. Par défaut : \r\n
const text = decodeEditorText(token.get('gmnotes'),{separator:'<BR>'});
asArray-- indique de renvoyer les lignes sous forme de tableau. Valeur par défaut :faux
const text = decodeEditorText(token.get('gmnotes'),{asArray:true});
REMARQUE : Les balises imbriquées<p>risquent de perturber et d'interrompre le décodage. Si vous rencontrez ce problème et avez besoin d'aide, veuillez contacterAaronpar message privé et il se fera un plaisir de vous aider.
obtenirCleanImgsrc
Dépendances :Aucune
À partir d'une URL d'image provenant d'un jeton ou d'une autre ressource, récupérez une version propre de celle-ci pouvant être utilisée pour créer un jeton via l'API, ouindéfiniesi elle ne peut pas être créée par l'API.
var getCleanImgsrc = function (imgsrc) {
var parts = imgsrc.match(/(.*\/images\/.*)(thumb|med|original|max)([^\?]*)(\?[^?]+)?$/);
if(parts) {
return parts[1]+'thumb'+parts[3]+(parts[4]?parts[4]:`?${Math.round(Math.random()*9999999)}`);
}
return;
};
Remarque :l'API ne peut créer que des images dont la source se trouve dans une bibliothèque utilisateur. L'imgsrcdoit également être la versionminiaturede l'image.
getSenderForName
Dépendances :Aucune
À partir d'un nom de chaîne, cette fonction renvoie une chaîne adaptée au premier paramètre de sendChat. Si un personnage partage le même nom qu'un joueur, c'est ce dernier qui sera utilisé. Vous pouvez également transmettre un objetoptions, dont la structure est identique à celle du paramètreoptionsde findObjs.
function getSenderForName(name, options) {
var personnage = findObjs({
type: 'character',
name: name
}, options)[0],
player = findObjs({
type: 'player',
displayname: name.lastIndexOf(' (MJ)') === name.length - 5 ? name.substring(0, name.length - 5) : name
}, options)[0];
if (player) {
return 'player|' + player.id;
}
if (personnage) {
return 'personnage|' + personnage.id;
}
return name;
}
obtenirCibleChuchotement
Dépendances :levenshteinDistance
À partir d'un ensemble d'options, cette fonction tente de construire la partie «/w nom » d'un chuchoter pour un appel à sendChat. Le paramètreoptionsdoit contenir soitplayer: true, soitpersonnage: true, ainsi qu'une valeur pouridouname. Les joueurs sont préférés aux personnages si les deux sont valides, et les identifiants sont préférés aux noms si les deux ont une valeur valide. Si un nom est fourni, le joueur ou le personnage dont le nom est le plus proche de la chaîne fournie recevra le message privé.
optionsest techniquement facultatif, mais si vous l'omettez (ou si vous ne fournissez pas une combinaison joueur/personnage + identifiant/nom), la fonction renverra une chaîne vide.
function getWhisperTarget(options) {
var nameProperty, targets, type;
options = options || {};
si (options.player) {xml-ph-0003@deepl.internamnProperty = 'displayname';xml-ph-0004@deepl.intern type = 'player';xml-ph-0005@deepl.intern} else if (options.character) {xml-ph-0006@deepl.internamnProperty = 'name';xml-ph-0007@deepl.intern type = 'character';xml-ph-0008@deepl.intern} else {xml-ph-0009@deepl.internreturn '';xml-ph-0010@deepl.intern}xml-ph-0011@deepl.internif (options.id) {xml-ph-0012@deepl.interntargets = [getObj(type, options.id)];xml-ph-0013@deepl.intern} if (targets[0]) {
return '/w ' + targets[0].get(nameProperty).split(' ')[0] + ' ';
}
}
if (options.name) {
// Trier tous les joueurs ou personnages (selon le cas) dont le nom contient le nom fourni,
// puis les trier en fonction de leur proximité avec le nom fourni.
cibles = _.sortBy(filterObjs(function(obj) {
si (obj.get('type') !== type) renvoyer faux ;
renvoyer obj.get(nameProperty).indexOf(options.name) >= 0 ;
}), function(obj) {
return Math.abs(levenshteinDistance(obj.get(nameProperty), options.name));
});
if (targets[0]) {
return '/w ' + targets[0].get(nameProperty).split(' ')[0] + ' ';
}
}
return '';
}
processusInlinerolls
Cette fonction analyseramsg.content et remplacera les rouleaux en ligne par leur résultat total. Ceci est particulièrement utile pour les commandes Mod (API) auxquelles l'utilisateur peut souhaiter transmettre des jets en ligne comme paramètres.
function processInlinerolls(msg) {
if (_.has(msg, 'inlinerolls')) {
return _.chain(msg.inlinerolls)
.reduce(function(previous, current, index) {
previous['$[[' + index + ']]'] = current.results.total || 0;
return previous;
},{})
.reduce(function(previous, current, index) {
return previous.replace(index, current);
}, msg.content)
.value();
} else {
return msg.content;
}
}
Voici une version légèrement plus complexe qui gère également la conversion des éléments de table en texte :
function processInlinerolls(msg) {
if(_.has(msg,'inlinerolls')){
return _.chain(msg.inlinerolls)
.reduce(function(m,v,k){
var ti=_.reduce(v.results.rolls,function(m2,v2){
if(_.has(v2,'table')){
m2.push(_.reduce(v2.results,function(m3,v3){
m3.push(v3.tableItem.name);
return m3;
},[]).join(', '));
}
return m2;
},[]).join(', ');
m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0;
return m;
},{})
.reduce(function(m,v,k){
return m.replace(k,v);
},msg.content)
.value();
} else {
return msg.content;
}
}
marqueurs d'état vers objet
L'inverse de objectToStatusmarkers ; transforme une chaîne pouvant être utilisée comme valeur de la propriétéstatusmarkersd'un objet jeton Roll20 en un objet JavaScript classique.
Veuillez noter qu'une chaîne de marqueurs d'état peut contenir des marqueurs d'état en double, tandis qu'un objet ne peut pas contenir de propriétés en double.
function statusmarkersToObject(stats) {
return _.reduce(stats.split(/,/), function(memo, value) {
var parts = value.split(/@/),
num = parseInt(parts[1] || '0', 10);
if (parts[0].length) {
memo[parts[0]] = Math.max(num, memo[parts[0]] || 0);
}
return memo;
}, {});
}
marqueurs d'état des objets
L'inverse de statusmarkersToObject ; transforme un objet JavaScript standard en une chaîne délimitée par des virgules pouvant être utilisée comme valeur de la propriétéstatusmarkersd'un objet jeton Roll20.
Veuillez noter qu'une chaîne de marqueurs d'état peut contenir des marqueurs d'état en double, tandis qu'un objet ne peut pas contenir de propriétés en double.
function objectToStatusmarkers(obj) {
return _.map(obj, function(value, key) {
return key === 'dead' || value < 1 || value > 9 ? key : key + '@' + parseInt(value);
})
.join(',');
}
Underscore.js
Le site web Underscore.js est davantage une référence API qu'un guide d'utilisation de la bibliothèque. Bien qu'il soit utile pour rechercher les fonctions disponibles et les paramètres qu'elles acceptent, il n'aide pas ceux qui cherchent à exploiter pleinement la puissance de la bibliothèque.
Collections
La rédaction de scripts implique souvent d'effectuerdes opérationssur un ensemble d'éléments. Lorsque nous évoquons les collections, il peut s'agir soit de tableaux :var foo = [0,1,10,"banana"];soit d'objets :var bar = { one: 1, two: 2, banana: 'fruit' };. Les tableaux sont indexés par des nombres (généralement à partir de 0 et en progressant), les objets possèdent des propriétés qui peuvent être utilisées comme index :bar['banana'] === 'fruit'; // vrai !. Les objets agissent de manière similaire aux tableaux associatifs d'autres langages.
Exemple de tableau :
var foo = [0,1,10,"banana"];
// Exemple d'objet
var bar = { one: 1, two: 2, banana: 'fruit' };
Appeler une fonction avec chaque élément [ _.each() ]
Il est très courant de devoir effectuer une opération sur chaque élément d'une collection. Généralement, les personnes utilisent des boucles« for» ou similaires. Underscore fournit _.each(), un moyen d'appeler une fonction avec chaque élément d'une collection comme argument.
_.each(foo, function(element){
log('element is '+element);
« élément est 0 »
« élément est 1 »
« élément est 10 »
« élément est banane »
Ce qui rend cette fonctionnalité si puissante, c'est que le même code fonctionne que vous utilisiez un tableau ou un objet :
_.each(bar, function(element){
log('element is '+element);
« élément est 1 »
« élément est 2 »
« élément est fruit »
Les fonctions n'ont pas besoin d'être en ligne. Ils reçoivent également des paramètres supplémentaires. (Veuillez consulter la documentation pour obtenir davantage de paramètres.) :
var logKeyValueMapping = function(value, key) {
log(key + " :: " + value);
};
log("An Array:");
_.each(foo, logKeyValueMapping);
log("An Object:");
_.each(bar, logKeyValueMapping);
« Un tableau : »
« 0 :: 0 »
« 1 :: 1 »
« 2 :: 10 »
« 3 :: banane »
« Un objet : »
« un :: 1 »
« deux :: 2 »
« banane :: fruit »
Transformation de chaque élément [ _.map() ]
La deuxième chose la plus courante à faire avec une collection est de transformer tous les articles qu'elle contient en articles d'un autre type. Souvent, les personnes procèdent ainsi en créant une autre collection, puis en utilisant une boucle« for »pour parcourir la première collection, transformer la valeur et la placer dans le nouveau conteneur. Il s'agit d'une quantité importante de code qui peut être simplifiée à l'aide de la fonction _.map() d'Underscore, qui permet d'appliquer une fonction à un ensemble d'éléments et d'obtenir un ensemble de résultats. Si cela ressemble à _.each(), c'est parce que c'est effectivement le cas, car il possède la même signature.
var res = _.map(foo, function(element){
return 'element is '+element;
});
log(res);
[« élément est 0 », « élément est 1 », « élément est 10 », « élément est banane »]
La fonction _.map() renvoie toujours un tableau contenant les résultats (voir la section Conversion des collections ci-dessous pour obtenir des objets). Tout comme _.each(), cette fonction accepte plusieurs arguments et peut être définie séparément.
[var getKeyValueMapping = function(value, key) {
return key + " :: " + value;
};
log("An Array:");
var resA = _.map(foo, getKeyValueMapping);
log(resA);
log("An Object:");
var resB_.map(bar, getKeyValueMapping);
log(resB);
« Un tableau : »
« ['0 :: 0', '1 :: 1', '2 :: 10', '3 :: banane'] »
« Un objet : »
« ['un :: 1', 'deux :: 2', 'banane :: fruit'] »