모드 스크립트(API): 레시피 모음집

다음은 완전한 대본이 아닙니다. 이들은 비즈니스 로직과 함께 결합되어 완전한 스크립트 생성을 지원하기 위한 것이지, 단독으로 스크립트를 생성하기 위한 것이 아닙니다.


모듈 패턴 공개

모듈 패턴은객체 내에 사적 멤버와 공적 멤버를 캡슐화함으로써 다른 언어의 클래스 개념을 모방합니다. 리빌링 모듈 패턴은 구문을 더 일관성 있게 만들어 모듈 패턴을 개선합니다.

var myRevealingModule = myRevealingModule || (function() {
    var privateVar = '이 변수는 비공개입니다',
        publicVar  = '이 변수는 공개입니다';

    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); // "이 변수는 public입니다"
myRevealingModule.myVar = '그래서 마음대로 변경할 수 있습니다';
log(myRevealingModule.myVar); // "그래서 마음대로 변경할 수 있습니다"

메모이제이션

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

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 호출을 생략할 수도 있습니다.

이 비동기 세마포어 구현체는 콜백에 대한 컨텍스트(this 값 설정)를 제공할 수 있을 뿐만 아니라 콜백에 매개변수를 전달할 수도 있습니다. 매개변수는 생성자에서 또는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) {
            // 세마포어 생성자에 전달된 인수를 sem.p(arg1, arg2, ...)로 재정의 허용
            if (arguments.length > 0) { parameters = arguments; }
            else { parameters = this.args; }

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

사용 예시:

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('첫 번째 sendChat 호출');
});
sendChat('', '/roll d20', function(ops) {
    log('두 번째 sendChat 실행 중');
    sem.p('두 번째 sendChat 호출');
});

예제 출력:

"두 번째 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형식으로 저장됩니다. 텍스트 전체에 걸쳐 나타나는 %## 코드 시퀀스로 이를 확인할 수 있습니다:

"에릭%20%28바이킹%2B과학자%29%20%5B파이터%3A%203%2C%20마법사%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));
        }
    );
}

유틸리티 함수

유틸리티 함수는 여러 스크립트에서 사용하고자 할 수 있는 일반적인 작업을 수행합니다. 스크립트 탭의 최상위 범위에 함수를 배치하면, 해당 함수가 모든 스크립트에서 사용 가능해져 오버헤드를 줄일 수 있습니다. 아래는 그러한 기능들의 일부입니다.

디코드 에디터 텍스트

의존성:없음

새로운 게임 내 텍스트 편집기는 꽤 괜찮지만, 데이터 세트 내 대형 텍스트 영역 중 하나에서 정보를 읽어오는 모드(API) 스크립트에는 문제가 발생합니다. 이 기능이 그 일을 돕습니다.

그래픽의gmnotes속성,캐릭터의bio또는gmnotes속성,핸드아웃의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;
};

첫 번째 인수는 처리할 텍스트입니다.

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

기본적으로 텍스트 줄은 \r\n.

선택적 두 번째 인수는 옵션이 포함된 객체입니다.

separator-- 텍스트 줄을 구분할 때 사용할 구분자를 지정합니다. 기본값: \r\n

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

 

asArray-- 대신 줄들을 배열로 반환하도록 지정합니다. 기본값:false

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

참고: 중첩된 `<` 태그( `>`)는 혼란을 초래하고 디코딩을 중단시킬 수 있습니다. 그 문제가 발생해서 도움이 필요하면,The Aaron에게개인 메시지를 보내세요. 그가 기꺼이 살펴볼 거예요.


깨끗한 이미지 소스 가져오기

의존성:없음

토큰이나 기타 리소스에서 가져온 이미지 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;
};

참고:API는 소스가 사용자 라이브러리에 위치한 이미지만 생성할 수 있습니다. 이미지의imgsrc는반드시 해당 이미지의축소판버전이어야 합니다.


getSenderForName

의존성:없음

주어진 문자열 이름을 바탕으로, 이 함수는 sendChat의 첫 번째 매개변수에 적합한 문자열을 반환합니다. 플레이어와 이름이 같은 캐릭터가 존재할 경우, 해당 플레이어가 사용됩니다. 또한 findObjs의options매개변수와 동일한 구조의options객체를 전달할 수도 있습니다.

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

의존성:levenshteinDistance

주어진 옵션 집합을 바탕으로, 이 함수는 sendChat 호출을 위한 귓속말의 "/w name" 부분을 구성하려고 시도합니다. options매개변수에는player: true또는character: true중 하나와id또는name 값이 포함되어야 합니다. 플레이어와 캐릭터가 모두 존재할 경우 플레이어를 우선하며, ID와 이름이 모두 유효한 값을 가질 경우 ID를 우선합니다. 이름이 지정된 경우, 지정된 문자열과 가장 유사한 이름을 가진 플레이어 또는 캐릭터에게 귓속말이 전송됩니다.

options는기술적으로 선택 사항이지만, 이를 생략하거나(또는 플레이어/캐릭터 + 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 '';
}

공정 인라인 롤

이 함수는msg.content를 스캔하여 인라인 롤을 총합 결과로 대체합니다. 이는 특히 사용자가 인라인 주사 결과를 매개변수로 전달하고자 할 수 있는 모드(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;
    }
}

다음은 테이블 항목을 텍스트로 변환하는 기능도 포함된 약간 더 복잡한 버전입니다:

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

상태 표시자를 객체로 변환

objectToStatusmarkers의 역함수; Roll20 토큰 객체의statusmarkers속성 값으로 사용하기 적합한 문자열을 평범한 자바스크립트 객체로 변환합니다.

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

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

객체 상태 마커

statusmarkersToObject의 역함수; 일반 자바스크립트 객체를 Roll20 토큰 객체의statusmarkers속성 값으로 사용하기에 적합한 쉼표로 구분된 문자열로 변환합니다.

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

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

 


언더스코어.js

The Underscore.js 웹사이트는 라이브러리 사용 가이드라기보다는 API 참조 문서에 가깝습니다. 사용 가능한 함수와 해당 함수가 받아들이는 매개변수를 확인하는 데 유용하지만, 라이브러리의 기능을 최대한 활용하려는 사람에게는 도움이 되지 않습니다.

컬렉션

스크립트 작성은 종종여러 개체 집합에 대해어떤 작업을수행하는 것을 포함한다. 컬렉션에 대해 이야기할 때, 이는 배열일 수도 있고 객체일 수도 있습니다:배열: var foo = [0,1,10,"banana"];객체:var bar = { one: 1, two: 2, banana: 'fruit' }; 배열은 숫자로 인덱싱됩니다(보통 0부터 시작하여 증가). 객체는 인덱스로 사용할 수 있는 속성을 가집니다:바['banana'] === 'fruit'; // true!. 객체는 다른 언어의 연관 배열과 유사하게 작동합니다.

샘플 데이터
샘플 배열:
var foo = [0,1,10,"banana"];

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

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

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

이것이 강력한 이유는 동일한 코드가 배열을 사용하든 객체를 사용하든 상관없이 작동하기 때문입니다:

_.each(바, function(element){
  log('element is ' + element);
"요소는 1"
"요소는 2"
"요소는 과일"

함수는 인라인으로 작성할 필요가 없습니다. 그들은 또한 추가 매개변수를 받습니다. (더 많은 매개변수에 대해서는 문서를 참조하십시오.):

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

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

log("An Object:");
_.each(bar, logKeyValueMapping);
"배열:"
"0 :: 0"
"1 :: 1"
"2 :: 10"
"3 :: banana"
"객체:"
"one :: 1"
"two :: 2"
"banana :: fruit"
각 요소 변환 [ _.map() ]

컬렉션에 대해 수행하는 두 번째로 흔한 작업은 포함된 모든 항목을 다른 유형의 항목으로 변환하는 것입니다. 사람들은 종종 다른 컬렉션을 만든 다음,for루프를 사용해 첫 번째 컬렉션을 순회하며 값을 변환하고 새 컨테이너에 푸시하는 방식으로 이를 수행할 수 있습니다. 이 많은 코드는 언더스코어의_.map()을 사용해 간소화할 수 있습니다._.map()은 함수를 요소 집합 전체에 적용하여 결과 집합을 얻는 방법입니다. 그것이 _.each()와 비슷하게 들린다면, 사실 그렇기 때문입니다. 동일한 시그니처를 가집니다.

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

_.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);
"배열:"
"['0 :: 0', '1 :: 1', '2 :: 10', '3 :: banana']"
"객체:"
"['one :: 1', 'two :: 2', 'banana :: fruit']"
도움이 되었습니까?
19명 중 17명이 도움이 되었다고 했습니다.