모드 (API): Cookbook

다음은 완전한 스크립트가 아닙니다. 이들은 완전한 스크립트 생성을 돕기 위해 비즈니스 로직과 함께 연결되어야 하며, 스크립트를 독립적으로 생성하지는 않습니다.


공개 모듈 패턴

모듈 패턴은 객체 내에서 비공개 및 공개 멤버를 캡슐화하여 다른 언어의 클래스 개념을 에뮬레이트합니다. 공개 모듈 패턴은 구문을 더 일관성 있게 만들어 기존 모듈 패턴을 개선합니다.

var myRevealingModule = myRevealingModule || (function() {
    var privateVar = 'This variable is private',
        publicVar  = 'This variable is public';

    function privateFunction() {
        log(privateVar);
    }

    function publicSet(text) {
        privateVar = text;
    }

    function publicGet() {
        privateFunction();
    }

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

log(myRevealingModule.getFunc()); // "This variable is private"
myRevealingModule.setFunc('But I can change its value');
log(myRevealingModule.getFunc()); // "But I can change its value"

log(myRevealingModule.myVar); // "This variable is public"
myRevealingModule.myVar = 'So I can change it all I want';
log(myRevealingModule.myVar); // "So I can change it all I want"

Memoization

메모이제이션은 주어진 입력에 대한 결과를 저장하여 두 번 계산하지 않고도 동일한 출력을 생성할 수 있는 최적화 기술입니다. 특히 비용이 많이 드는 계산에 유용합니다. 물론 함수가 동일한 입력을 받는 경우가 드물다면 메모이제이션은 제한된 유용성을 가지며, 메모리 요구 사항은 계속해서 증가합니다.

var factorialCache = {};
function factorial(n) {
    var x;

    n = parseInt(n || 0);
    if (n < 0) {
        throw '음수의 팩토리얼은 잘 정의되지 않습니다.';
    }

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

    x = factorial(n - 1) * n;
    factorialCache[n] = x;
    return x;
}

Roll20 모드(API) 스크립트에서 캐시된 값은 state에 저장될 수 있으며, 이는 게임 세션 간에 지속됩니다. 그러나 많은 수의 잠재적 입력이 있는 경우 Roll20에서상태의 사용을 제한할 수 있습니다.


비동기 세마포어

비동기 세마포어는 비동기 작업 (예: sendChat 호출) 집합이 완료된 후에 콜백 메서드를 실행할 수 있게 해줍니다. 어떤 순서로작업이 완료될지는 보장할 수 없지만, 세마포어의 콜백이 실행될 때 모든 작업()이완료되었다는 것은 보장할 수 있습니다.

세마포어를 사용하는 경우 각 비동기 연산을 호출하기 전에v()를 호출하고, 각 비동기 연산의 마지막 문으로p()를 호출합니다. 수행하려는 연산 수를 미리 알고 있다면 세마포어 생성자에 해당 수를 제공하고v로의 호출을 생략할 수도 있습니다.

비동기 세마포어의 이 특별한 구현을 통해 콜백에 대한 컨텍스트를 제공하고(값 설정), 콜백에 매개변수를 전달할 수도 있습니다. 매개 변수는 생성자 또는p으로의 호출에서 지정할 수 있습니다. (p의 매개 변수는 생성자의 매개 변수보다 우선합니다.)

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) {
            // allow sem.p(arg1, arg2, ...) to override args passed to Semaphore constructor
            if (arguments.length > 0) { parameters = arguments; }
            else { parameters = this.args; }

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

Example of use:

var sem = new Semaphore(function(lastAsync) {
    log(lastAsync + ' completed last');
    log(this);
}, 2, { foo: 'bar', fizz: 'buzz' }, 'Sir not appearing in this callback');

sendChat('', '/roll d20', function(ops) {
    log('Executing first sendChat');
    sem.p('First sendChat call');
});
sendChat('', '/roll d20', function(ops) {
    log('Executing second sendChat');
    sem.p('Second sendChat call');
});

예시 출력:

"두 번째 sendChat 실행"
"첫 번째 sendChat 실행"
"첫 번째 sendChat 호출이 마지막으로 완료됨"
{ foo: "bar", fizz: "buzz" }

핸드아웃 & 캐릭터

핸드아웃 만들기

핸드아웃 텍스트 블록이 처리되는 방식 때문에, 핸드아웃 객체를 만드는 데는 두 단계가 필요합니다. 먼저 객체를 생성한 다음 텍스트 블록을 설정합니다.

//모든 플레이어에게 사용 가능한 새로운 핸드아웃을 생성합니다.
    var handout = createObj("handout", {
                name: "핸드아웃의 이름",
                inplayerjournals: "all",
                archived: false
    });
    handout.set('notes', '핸드아웃이 생성된 후에 노트를 설정해야 합니다.');
    handout.set('gmnotes', '핸드아웃이 생성된 후에 GM 노트를 설정해야 합니다.');

인코딩 처리

핸드아웃의 텍스트 블록(노트 및 GM 노트)과 캐릭터의 텍스트 블록(바이오 및 GM 노트)은 사용자 인터페이스를 통해 설정된 것이며 x-www-form-urlencoded 형식으로 저장됩니다. 이를 인식하는 방법은 텍스트 전체에 걸쳐 %## 코드의 시퀀스가 있는지 확인하는 것입니다.

"Erik%20%28Viking%2BScientist%29%20%5BFighter%3A%203%2C%20Wizard%3A%202%5D"

이 텍스트는 채팅으로 전송할 수 있으며 브라우저에서 번역됩니다. 그러나 텍스트를 수정해야 하는 경우 입력한 대로 처리해야 할 수도 있습니다.

에릭 (바이킹+과학자) [전사: 3, 마법사: 2]"

다음 함수를 사용하여 인코딩된 텍스트를 디코딩할 수 있습니다:

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

유틸리티 함수

유틸리티 함수는 여러 스크립트에서 사용할 수 있는 일반적인 작업을 완료합니다. 스크립트 탭의 가장 바깥 범위에 함수를 배치하면 해당 함수는 모든 스크립트에서 사용할 수 있으며 오버헤드를 줄일 수 있습니다. 다음은 해당 함수의 일부입니다.

decodeEditorText

Dependencies:None

새로운 게임 내 텍스트 편집기는 꽤 좋지만, 데이터 세트 중 하나의 큰 텍스트 영역에서 정보를 읽는 데 의존하는 Mod (API) 스크립트에 문제가 발생합니다. 이 기능은 그것을 돕습니다.

텍스트를 제공 받은 경우Graphic'gmnotes 속성 또는Character'bio 또는gmnotes 속성 또는Handout'notes 또는gmnotes 속성에서, 이는 편집자 서식이 자동으로 삽입 된 버전을 제거한 버전을 반환합니다.

const decodeEditorText = (t, o) =>{
  let w = t;
  o = Object.assign({ separator: '\r\n', asArray: false },o);
  /* 토큰 GM 노트 */
  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 ? lines : lines.join(o.separator);
  }
  /* neither */
  return t;
};

The first argument is the text to process.

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

By default, the lines of text will be separated by \r\n.

선택적인 두 번째 인수는 옵션을 담은 객체입니다.

구분 기호 -- 텍스트 줄을 구분하는 데 사용할 문자를 지정합니다. 기본값: \r\n

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

 

배열로 반환 -- 줄을 배열로 반환합니다. 기본값: false

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

참고: 중첩된 <p> 태그는 디코딩을 혼란스럽게하고 망칠 수 있습니다. 만약 문제가 발생하고 도움이 필요한 경우, PMThe Aaron에게 문의하면 기쁘게도 확인해 볼 것입니다.


getCleanImgsrc

Dependencies:None

토큰이나 다른 자원에서 가져온 이미지 URL을 사용하여 API를 통해 토큰을 생성할 수 있는 깨끗한 버전을 얻거나, API에서 생성할 수 없는 경우undefined을 반환합니다.

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

Note:API는 사용자 라이브러리에 소스가 있는 이미지만 생성할 수 있습니다. imgsrc또한 이미지의thumb버전이어야 합니다.


getSenderForName

Dependencies:None

Given a string name, this function will return a string appropriate for the first parameter of sendChat. If there is a character that shares a name with a player, the player will be used. You may also pass anoptionsobject, which is structured identically to theoptionsparameter of findObjs.

function getSenderForName(name, options) {
    var character = findObjs({
            type: 'character',
            name: name
        }, options)[0],
        player = findObjs({
            type: 'player',
            displayname: name.lastIndexOf(' (GM)') === name.length - 5 ? name.substring(0, name.length - 5) : name
        }, options)[0];
    
    if (player) {
        return 'player|' + player.id;
    }
    if (character) {
        return 'character|' + character.id;
    }
    return name;
}

getWhisperTarget

Dependencies:levenshteinDistance

Given a set of options, this function tries to construct the "/w name" portion of a whisper for a call to sendChat. Theoptionsparameter should contain eitherplayer: trueorcharacter: trueand a value for eitheridorname. Players are preferred over characters if both are true, and ids are preferred over names if both have a valid value. 이름이 제공되면, 제공된 문자열과 가장 가까운 이름을 가진 플레이어 또는 캐릭터에게 귓속말이 전송됩니다.

옵션은 기술적으로 선택 사항이지만, 이를 생략하거나(또는 플레이어/캐릭터 + id/이름의 조합을 제공하지 않는 경우) 함수는 빈 문자열을 반환합니다.

function getWhisperTarget(options) {
    var nameProperty, targets, type;
    
    options = options || {};
    
    if (options.player) {
        nameProperty = 'displayname';
        type = 'player';
    } else if (options.character) {
        nameProperty = 'name';
        type = 'character';
    } else {
        return '';
    }
    
    if (options.id) {
        targets = [getObj(type, options.id)];
        
        if (targets[0]) {
            return '/w ' + targets[0].get(nameProperty).split(' ')[0] + ' ';
        }
    }
    if (options.name) {
        // 제공된 이름을 *포함하는* 모든 플레이어 또는 캐릭터(적절한 경우)를 정렬하고,
        // 제공된 이름과 얼마나 가까운지에 따라 정렬합니다.
        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 '';
}

processInlinerolls

이 함수는 msg.content를 스캔하고 인라인 롤을 결과 값으로 대체합니다. 이것은 사용자가 인라인 롤을 매개변수로 전달하고 싶어하는 Mod(API) 명령에 대해 특히 유용합니다.

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

Here is a slightly more complicated version which also handles converting tableItems to their text:

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

objectToStatusmarkers의 역; Roll20 토큰 객체의 statusmarkers 속성의 값으로 사용할 수 있는 문자열을 일반 JavaScript 객체로 변환합니다.

상태 표시기 문자열에는 중복된 상태 표시기가 포함될 수 있지만, 객체에는 중복된 속성이 포함될 수 없습니다.

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

statusmarkersToObject의 역; 일반 JavaScript 객체를 쉼표로 구분된 문자열로 변환하여 Roll20 토큰 객체의 statusmarkers 속성의 값으로 사용할 수 있습니다.

상태 표시기 문자열에는 중복된 상태 표시기가 포함될 수 있지만, 객체에는 중복된 속성이 포함될 수 없습니다.

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

 


Underscore.js

TheUnderscore.jswebsite is more of an API reference than a guide to using the library. While useful for looking up what functions are available and what parameters they accept, it doesn't help someone trying to break into using the library's power to its fullest extent.

Collections

Writing scripts often involves doingsomethingto a collection ofthings. When we talk about collections, they can either be arrays:var foo = [0,1,10,"banana"];or objects:var bar = { one: 1, two: 2, banana: 'fruit' };. Arrays are indexed by numbers (usually starting from 0 and counting up), objects have properties which can be used for indexes:bar['banana'] === 'fruit'; // true!. Objects effectively act like associative arrays from other languages.

Sample Data
샘플 배열:
var foo = [0,1,10,"바나나"];

// 샘플 객체
var bar = { one: 1, two: 2, banana: 'fruit' };
각 요소와 함께 함수 호출 [ _.each() ]

컬렉션의 각 요소에 대해 어떤 작업을 수행해야 하는 경우가 매우 흔합니다. 보통 사람들은루프나 유사한 작업에 대해을 사용합니다. Underscore는 컬렉션의 각 요소를 인수로 사용하여 함수를 호출하는 방법인_.each()를 제공합니다.

_.each(foo, function(element){
  log('element is '+element);
"element is 0"
"element is 1"
"element is 10"
"element is banana"

이것이 가능한 이유는 동일한 코드가 배열 또는 객체를 사용하는지 여부에 관계없이 작동하기 때문입니다.

_.each(bar, function(element){
  log('element is '+element);
"element is 1"
"element is 2"
"element is fruit"

Functions do not need to be inline. They also receive additional parameters. (See documentation for even more parameters.):

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

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

log("An Object:");
_.each(bar, logKeyValueMapping);
"An Array:"
"0 :: 0"
"1 :: 1"
"2 :: 10"
"3 :: banana"
"An Object:"
"one :: 1"
"two :: 2"
"banana :: fruit"
Transforming Each Element [ _.map() ]

The next most common thing to do with a collection is to transform all of the contained items into items of another type. 다른 컬렉션을 만든 다음for루프를 사용하여 첫 번째 컬렉션을 반복하고 값을 변환하여 새 컨테이너에 푸시하는 방식으로 이 작업을 수행하는 경우가 많습니다. 요소 모음에 함수를 적용하고 결과 모음을 가져오는 방법인 밑줄의_.map()을 사용하면 많은 코드를 간소화할 수 있습니다. 만약 이게 _.each()와 비슷하게 들린다면, 그것은 사실 그와 동일한 서명을 갖고 있기 때문입니다.

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

_.map()의 반환값은 항상 결과의 배열입니다 (객체를 얻으려면 아래의 '컬렉션 변환'을 참조). 또한 _.each()와 마찬가지로 함수는 더 많은 인수를 받고 별도로 정의할 수 있습니다.

[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);
"An Array:"
"['0 :: 0', '1 :: 1', '2 :: 10', '3 :: banana']"
"An Object:"
"['one :: 1', 'two :: 2', 'banana :: fruit']"
도움이 되었습니까?
14명 중 12명이 도움이 되었다고 했습니다.