API: Kochbuch

Bei den folgenden Skripten handelt es sich nicht um vollständige Skripte. Sie sollen zusammen mit der Geschäftslogik zusammengefügt werden, um die Erstellung vollständiger Skripte zu unterstützen, und nicht dazu, eigene Skripte zu erstellen.


Aufschlussreiches Modulmuster

DasModulmusteremuliert das Konzept von Klassen aus anderen Sprachen, indem private und öffentliche Mitglieder in einem Objekt gekapselt werden. Das Revealing Module Pattern verbessert das Module Pattern, indem es die Syntax konsistenter macht.

var myRevealingModule = myRevealingModule || (function() {
    var privateVar = 'Diese Variable ist privat',
        publicVar = 'Diese Variable ist öffentlich';

    function privateFunction() {
        log(privateVar);
    }

    function publicSet(text) {
        privateVar = text;
    }

    function publicGet() {
        privateFunction();
    }

    return {
        setFunc: publicSet,
        myVar: publicVar,
        getFunc: publicGet
    };
}());

log(myRevealingModule.getFunc()); // „Diese Variable ist privat“
myRevealingModule.setFunc('Aber ich kann ihren Wert ändern');
log(myRevealingModule.getFunc()); // „Aber ich kann seinen Wert ändern“

log(myRevealingModule.myVar); // "Diese Variable ist öffentlich"
myRevealingModule.myVar = 'Damit ich alles ändern kann, was ich will';
log(myRevealingModule.myVar); // „Damit ich alles ändern kann, was ich will“

Auswendiglernen

Memoisierung ist eine Optimierungstechnik, die das Ergebnis für eine bestimmte Eingabe speichert und es ermöglicht, dieselbe Ausgabe zu erzeugen, ohne sie zweimal berechnen zu müssen. Dies ist besonders bei aufwendigen Berechnungen nützlich. Wenn es selten vorkommt, dass Ihre Funktion dieselben Eingaben erhält, ist die Memoisierung natürlich nur von begrenztem Nutzen, während der Speicherbedarf dafür weiter wächst.

var factialCache = {};
Funktion Fakultät(n) {
    var x;

    n = parseInt(n || 0);
    if (n < 0) {
        throw 'Fakultäten negativer Zahlen sind nicht wohldefiniert';
    }

    if (n === 0) {
        return 1;
    } else if (factorialCache[n]) {
        return factorialCache[n];
    }

    x = Fakultät(n - 1) * n;
    faktorielleCache[n] = x;
    Rückkehr x;
}

In einem Roll20-API-Skript könnten die zwischengespeicherten Werte möglicherweise imZustandgespeichert werden, der zwischen Spielsitzungen bestehen bleibt. Wenn Sie jedoch über eine große Anzahl potenzieller Eingaben verfügen, beachten Sie, dass Roll20 Ihre Verwendung vonstatedrosseln kann.


Asynchrones Semaphor

Mit einem asynchronen Semaphor können Sie eine Rückrufmethode auslösen, nachdem eine Reihe asynchroner Vorgänge (z. B. Aufrufe von sendChat) abgeschlossen wurden. Sie können zwar nicht garantieren, in welcher Reihenfolgedie Vorgänge abgeschlossen werden, Sie können jedoch garantieren, dass alleabgeschlossen sindwenn der Rückruf des Semaphors ausgelöst wird.

Wenn Sie ein Semaphor verwenden, rufen Siev()vor dem Aufruf jeder asynchronen Operation auf und rufen Siep()als letzte Anweisung jeder asynchronen Operation auf. Wenn die Anzahl der Operationen, die Sie ausführen möchten, im Voraus bekannt ist, können Sie diese Zahl auch dem Semaphorkonstruktor übergeben und die Aufrufe vonvweglassen.

Mit dieser speziellen Implementierung eines asynchronen Semaphors können Sie auch einen Kontext für den Rückruf bereitstellen (den Wertaufsetzen) sowie Parameter an den Rückruf übergeben. Die Parameter können entweder im Konstruktor oder im Aufruf vonpangegeben werden. (Parameter inphaben Vorrang vor Parametern im Konstruktor.)

function Semaphore(callback, initial, context) {
    var args = (arguments.length === 1 ? [Argumente[0]]: Array.apply(null, Argumente));

    this.lock = parseInt(initial, 10) || 0;
    this.callback = Rückruf;
    this.context = Kontext || Ruf zurück;
    this.args = args.slice(3);
}
Semaphore.prototype = {
    v: function() { this.lock++; },
    p: function() {
        var-Parameter;

        this.lock--;

        if (this.lock === 0 && this.callback) {
            // sem.p(arg1, arg2, ...) erlauben, an den Semaphore-Konstruktor übergebene Argumente zu überschreiben
            if (arguments.length > 0) { Parameter = Argumente; }
            else { Parameter = this.args; }

            this.callback.apply(this.context, Parameters);
        }
    }
};

Anwendungsbeispiel:

var sem = new Semaphore(function(lastAsync) {
    log(lastAsync + 'letzt abgeschlossen');
    log(this);
}, 2, { foo: 'bar', fizz: 'buzz' }, 'Sir erscheint nicht in diesem Rückruf');

sendChat('', '/roll d20', function(ops) {
    log('Executing first sendChat');
    sem.p('First sendChat call');
});
sendChat('', '/roll d20', function(ops) {
    log('Ausführen des zweiten sendChat');
    sem.p('Zweiter sendChat-Aufruf');
});

Beispielausgabe:

„Zweiter sendChat wird ausgeführt“
„Erster sendChat wird ausgeführt“
„Erster sendChat-Aufruf zuletzt abgeschlossen“
{ foo: „bar“, fizz: „buzz“ }

Handouts & Zeichen

Erstellen eines Handouts

Aufgrund der Art und Weise, wie Handout-Textblöcke gehandhabt werden, muss die Erstellung eines Handout-Objekts in zwei Schritten erfolgen: Erstellen Sie zuerst das Objekt und legen Sie dann die Textblöcke fest:

//Erstelle ein neues Handout, das allen Spielern zur Verfügung steht
    var handout = createObj("handout", {
                name: "Der Name des Handouts",
                inplayerjournals: "all",
                archived: false
    });
    handout.set('notes', 'Notizen müssen nach der Erstellung des Handouts festgelegt werden.');
    handout.set('gmnotes', 'GM-Notizen müssen ebenfalls festgelegt werden, nachdem das Handout erstellt wurde.');

Umgang mit der Kodierung

Die Textblöcke in Handouts (Notizen und GM-Notizen) und Charakteren (Bio- und GM-Notizen), die über die Benutzeroberfläche festgelegt werden, werden im Formatx-www-form-urlencodedgespeichert. Sie erkennen dies an der Reihenfolge der %##-Codes im gesamten Text:

„Erik%20%28Wikinger%2BWissenschaftler%29%20%5BKämpfer%3A%203%2C%20Zauberer%3A%202%5D“

Dieser Text kann an den Chat gesendet werden und wird vom Browser übersetzt. Wenn Sie jedoch Änderungen am Text vornehmen müssen, möchten Sie ihn möglicherweise so behandeln, wie er eingegeben wurde:

Erik (Wikinger+Wissenschaftler) [Kämpfer: 3, Zauberer: 2]“

Sie können den codierten Text mit der folgenden Funktion dekodieren:

var decodeUrlEncoding = function(t){
  return t.replace(
        /%([0-9A-Fa-f]{1,2})/g,
        function(f,n){
            return String.fromCharCode(parseInt(n,16));
        }
    ) ;
}

Dienstprogrammfunktionen

Hilfsfunktionen erledigen allgemeine Aufgaben, die Sie möglicherweise in vielen Skripts verwenden möchten. Wenn Sie eine Funktion im äußersten Bereich einer Skriptregisterkarte platzieren, sollte diese Funktion für alle Ihre Skripte verfügbar sein, wodurch sich Ihr Overhead verringert. Nachfolgend finden Sie eine Auswahl solcher Funktionen.

decodeEditorText

Abhängigkeiten:Keine

Der neue Texteditor im Spiel ist ziemlich nett, bringt jedoch ein Problem mit API-Skripten mit sich, die darauf angewiesen sind, Informationen aus einem der großen Textbereiche im Datensatz zu lesen. Dabei hilft diese Funktion.

Wenn der Text aus einer EigenschaftGraphic'sgmnotesoder einer EigenschaftCharacter'sbioodergmnotesoder einer EigenschaftHandout'sNotesodergmnotesstammt, wird dies zurückgegeben eine Version, bei der die Formatierung des automatisch eingefügten Editors entfernt wurde.

const decodeEditorText = (t, o) =>{
  let w = t;
  o = Object.assign({ Separator: '\r\n', asArray: false },o);
  /* Token GM Notes */
  if(/^%3Cp%3E/.test(w)){
    w = unescape(w);
  }
  if(/^<p>/.test(w)){
    letlines = w.match(/<p>.*?<\/p>/g)
      .map( l => l.replace(/^<p>(.*?)<\/p>$/,'$1'));
    return o.asArray ? Zeilen:lines.join(o.separator);
  }
  /* weder */
  return t;
};

Das erste Argument ist der zu verarbeitende Text.

const text = decodeEditorText(token.get('gmnotes'));

Standardmäßig werden die Textzeilen durch\r\ngetrennt.

Das optionale zweite Argument ist ein Objekt mit Optionen.

Trennzeichen– gibt an, womit Textzeilen getrennt werden sollen. Standard:\r\n

const text = decodeEditorText(token.get('gmnotes'),{separator:'<BR>'});

 

asArray– gibt an, die Zeilen stattdessen als Array zurückzugeben. Standard:false

const text = decodeEditorText(token.get('gmnotes'),{asArray:true});

HINWEIS:Verschachtelte<p>Tags verwirren und unterbrechen die Dekodierung. Wenn Sie auf dieses Problem stoßen und Hilfe benötigen, senden Sie eine PN anThe Aaronund er wird sich das gerne ansehen.


getCleanImgsrc

Abhängigkeiten:Keine

Erhalten Sie bei gegebener Bild-URL, die von einem Token oder einer anderen Ressource stammt, eine saubere Version davon, die zum Erstellen eines Tokens über die API verwendet werden kann, oderundefiniertwenn sie nicht von der API erstellt werden kann.

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;
};

Hinweis:Die API ist nur in der Lage, Bilder zu erstellen, deren Quelle sich in einer Benutzerbibliothek befindet. Dieimgsrcmuss auch dieThumbVersion des Bildes sein.


getSenderForName

Abhängigkeiten:Keine

Bei einem gegebenen String-Namen gibt diese Funktion einen String zurück, der für den ersten Parameter von sendChat geeignet ist. Wenn es einen Charakter gibt, der einen Namen mit einem Spieler teilt, wird der Spieler verwendet. Sie können auch ein ObjektOptionenübergeben, das identisch zum ParameterOptionenvon findObjs aufgebaut ist.

function getSenderForName(name, options) {
    var Character = findObjs({
            type: 'character',
            name: name
        }, options)[0],
        player = findObjs({
            type: 'player',
            displayname: name.lastIndexOf(' (GM)') === name .Länge - 5 ? name.substring(0, name.length - 5): name
        }, Optionen)[0];
    
    if (player) {
        return 'player|' + Spieler.id;
    }
    if (character) {
        return 'character|' + charakter.id;
    }
    Rückgabename;
}

getWhisperTarget

Abhängigkeiten:levenshteinDistance

Bei einer Reihe von Optionen versucht diese Funktion, den „/w name“-Teil eines Whispers für einen Aufruf von sendChat zu erstellen. Der ParameterOptionensollte entwederSpieler: wahroderZeichen: wahrund einen Wert für entwederIDoderNameenthalten. Spieler werden gegenüber Charakteren bevorzugt, wenn beide wahr sind, und IDs werden gegenüber Namen bevorzugt, wenn beide einen gültigen Wert haben. Wenn ein Name angegeben wird, wird der Flüsterton an den Spieler oder Charakter gesendet, dessen Name der angegebenen Zeichenfolge am nächsten kommt.

Optionenist technisch optional, aber wenn Sie es weglassen (oder keine Kombination aus Spieler/Charakter + ID/Name angeben), gibt die Funktion eine leere Zeichenfolge zurück.

function getWhisperTarget(options) {
    var nameProperty, targets, type;
    
    Optionen = Optionen || {};
    
    if (options.player) {
        nameProperty = 'displayname';
        Typ = 'Spieler';
    } else if (options.character) {
        nameProperty = 'name';
        Typ = 'Zeichen';
    } else {
        return '';
    }
    
    if (options.id) {
        targets = [getObj(type, options.id)];
        
        if (targets[0]) {
            return '/w ' + targets[0].get(nameProperty).split(' ')[0] + ' ';
        }
    }
    if (options.name) {
        // Alle Spieler oder Charaktere (je nach Bedarf) sortieren, deren Name den angegebenen Namen *enthält*,
        // dann danach sortieren, wie nah sie am angegebenen Namen sind.
        targets = _.sortBy(filterObjs(function(obj) {
            if (obj.get('type') !== type) return false;
            return 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 '';
}

ProzessInlinerollen

Diese Funktion durchsuchtmsg.contentund ersetzt Inline-Rolls durch ihr Gesamtergebnis. Dies ist besonders nützlich für API-Befehle, an die der Benutzer möglicherweise Inline-Rolls als Parameter übergeben möchte.

functionprocessInlinerolls(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;
    }
}

Hier ist eine etwas kompliziertere Version, die auch die Konvertierung von tableItems in ihren Text übernimmt:

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;
	}
}
 

statusmarkersToObject

Die Umkehrung von objectToStatusmarkers; wandelt eine Zeichenfolge, die zur Verwendung als Wert der Eigenschaftstatusmarkerseines Roll20-Token-Objekts geeignet ist, in ein einfaches altes JavaScript-Objekt um.

Beachten Sie, dass eine Statusmarkerzeichenfolge doppelte Statusmarker enthalten kann, während ein Objekt keine doppelten Eigenschaften enthalten kann.

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;
    }, {} );
}

objectToStatusmarkers

Die Umkehrung von statusmarkersToObject; Wandelt ein einfaches altes JavaScript-Objekt in eine durch Kommas getrennte Zeichenfolge um, die zur Verwendung als Wert der Eigenschaftstatusmarkerseines Roll20-Token-Objekts geeignet ist.

Beachten Sie, dass eine Statusmarkerzeichenfolge doppelte Statusmarker enthalten kann, während ein Objekt keine doppelten Eigenschaften enthalten kann.

function objectToStatusmarkers(obj) {
    return _.map(obj, function(value, key) {
                return key === 'dead' || value < 1 || value > 9 ? Schlüssel: Schlüssel + '@' + parseInt(value);
            })
            .join(',');
}

 


Underscore.js

Die WebsiteUnderscore.jsist eher eine API-Referenz als eine Anleitung zur Verwendung der Bibliothek. Es ist zwar nützlich, um herauszufinden, welche Funktionen verfügbar sind und welche Parameter sie akzeptieren, aber es hilft niemandem, der versucht, die Leistungsfähigkeit der Bibliothek in vollem Umfang zu nutzen.

Sammlungen

Beim Schreiben von Skripten geht es oft darum,etwasfür eine Sammlung vonDingenzu tun. Wenn wir über Sammlungen sprechen, können sie entweder Arrays sein:var foo = [0,1,10,"banana"];oder Objekte:var bar = { one: 1, two: 2, banana: 'fruit' };. Arrays werden durch Zahlen indiziert (normalerweise beginnend bei 0 und hochzählend), Objekte haben Eigenschaften, die für Indizes verwendet werden können:bar['banana'] === 'fruit'; // WAHR!. Objekte verhalten sich praktisch wie assoziative Arrays aus anderen Sprachen.

Beispieldaten
Beispielarray:
var foo = [0,1,10,"banana"];

// Beispielobjekt
var bar = { one: 1, two: 2, banana: 'fruit' };
Aufrufen einer Funktion mit Each Element [ _.each() ]

Es kommt sehr häufig vor, dass mit jedem Element einer Sammlung eine Operation ausgeführt werden muss. Normalerweise verwenden die LeutefürSchleifen oder ähnliches. Underscore bietet_.each(), eine Möglichkeit, eine Funktion mit jedem Element einer Sammlung als Argument aufzurufen.

_.each(foo, function(element){
  log('element is '+element);
„Element ist 0“
„Element ist 1“
„Element ist 10“
„Element ist Banane“

Was dies so leistungsstark macht, ist, dass der identische Code funktioniert, unabhängig davon, ob Sie ein Array oder ein Objekt verwenden:

_.each(bar, function(element){
  log('element is '+element);
„Element ist 1“
„Element ist 2“
„Element ist Frucht“

Funktionen müssen nicht inline sein. Sie erhalten außerdem zusätzliche Parameter. (Weitere Parameter finden Sie in der Dokumentation.):

var logKeyValueMapping = function( value, key ) {
    log(key + " :: " + value);
};

log("Ein Array:");
_.each(foo, logKeyValueMapping);

log("Ein Objekt:");
_.each(bar, logKeyValueMapping);
„Ein Array:“
„0 :: 0“
„1 :: 1“
„2 :: 10“
„3 :: Banane“
„Ein Objekt:“
„eins :: 1“
„zwei: : 2"
"Banane :: Frucht"
Jedes Element transformieren [ _.map() ]

Die zweithäufigste Vorgehensweise bei einer Sammlung besteht darin, alle darin enthaltenen Elemente in Elemente eines anderen Typs umzuwandeln. Dies geschieht oft, indem man eine weitere Sammlung erstellt und dann einefür-Schleife verwendet, um die erste Sammlung zu durchlaufen, den Wert umzuwandeln und ihn in den neuen Container zu verschieben. Das ist eine Menge Code, der mit_.map()von Underscore vereinfacht werden kann, einer Möglichkeit, eine Funktion auf eine Sammlung von Elementen anzuwenden und eine Sammlung der Ergebnisse zu erhalten. Wenn das ähnlich klingt wie _.each(), liegt das daran, dass es tatsächlich dieselbe Signatur hat.

var res = _.map(foo, function(element){
  return 'element is '+element;
});
Log(res);
['Element ist 0','Element ist 1','Element ist 10','Element ist Banane']

Die Rückgabe von _.map() ist immer ein Array der Ergebnisse (Informationen zum Abrufen von Objekten finden Sie weiter unten unter „Konvertieren von Sammlungen“). Genau wie _.each() erhält die Funktion mehr Argumente und kann separat definiert werden.

[var getKeyValueMapping = function( value, key ) {
    return key + " :: " + value;
};

log("Ein Array:");
var resA = _.map(foo, getKeyValueMapping);
log(resA);

log("Ein Objekt:");
var resB_.map(bar, getKeyValueMapping);
log(resB);
"Ein Array:"
"['0 :: 0', '1 :: 1', '2 :: 10', '3 :: banane']"
"Ein Objekt:"
"['one :: 1‘, ‚zwei :: 2‘, ‚Banane :: Frucht‘]“
War dieser Beitrag hilfreich?
10 von 12 fanden dies hilfreich