시리즈로 돌아가기
1화: 로이스 API/매크로(26.02.17)

더블크로스 LOIS 자동 등록 API/매크로


API를 등록해두고 매크로를 사용하면, 자동으로 시트에 등록이 되고 챗창에 올라옵니다. 한번에 시나리오 로이스도 삭제 가능합니다.

단순하게 만든 것이니 추가적으로 수정하고 싶으면 편하게 수정해서 사용해주세요.



638779058885347865_0.gif


1. 준비물 

1) 더블크로스 시트

    - 아본님 시트: https://x.com/eggpowder_abon/status/1096955967915192320

    - roll20 기본 시트(by. 타텍님)


2) ROLL20 프로계정

     API를 필수로 사용합니다. 


3) AS(화자)

     AS(화자)를 반드시 캐릭터로 바꿔둡니다.



2. 사용 방법

1) 작성

 · !l 관계명,취득대상,p감정내용,n감정내용,p감정/n감정 

    혹은 !로 관계명,취득대상,p감정내용,n감정내용,p감정/n감정

 · 편한 방법: 하단의 미리 적어둔 매크로를 사용합니다. 


2) 삭제

 · !l delete를 입력하면 시나리오 로이스를 모두 삭제합니다. 

 · 고정 로이스는 삭제되지 않습니다.


2. 설치 방법

1-1) Roll20 방을 만들고, 시트 템플릿을 'Custom'으로 맞춘 다음 아본님의 더블크로스 시트로 설정합니다.

1-2) Roll20 방을 만들 때, 시트를 Double Cross 3rd로 맞춥니다. 타텍님의 더블크로스 시트입니다.


2) 설정 > 모드(API) 스크립트 > New Script를 누른 뒤 해당 API를 작성하고, Save Script 버튼을 눌러주세요.

clipboard_1771267020107.png

3) 게임에 입장하셔서, 매크로를 등록합니다.

clipboard_1771267030366.png


4) 반드시 AS를 사용할 캐릭터로 바꾸고, 매크로를 사용한 뒤 제대로 작동하는지 확인합니다. 

5) TRPG 세션에 사용합시다.


※쓸 때 주의사항

1. 반드시 AS는 로이스를 등록할 캐릭터로 맞춥니다.

2. 관계/이름/감정P/감정N/속성 중 한 칸이라도 내용이 있으면 다음 줄로 밀려서 작성되니, 반드시 PL들에게 줄이 비어있는지 확인 바랍니다.


아래로는 API와 매크로가 이어집니다.


* LOIS API(아본님)

on("chat:message", function(msg) {
  if (msg.type == "api" && (msg.content.indexOf("!l ") === 0 || msg.content.indexOf("!로 ") === 0)) {
    var character = findObjs({_type: "character", name: msg.who})[0];
    if (!character) {
      sendChat("API", "Error: 캐릭터를 찾을 수 없습니다.");
      return;
    }

    var attributeNums = ["lois1_", "lois2_", "lois3_", "lois4_", "lois5_", "lois6_", "lois7_"];
    var attributeSuffixes = ["rltn", "name", "pemo01", "nemo01", "elem"];
    var messageContent = msg.content.split(" ").slice(1).join(" ").trim();
    var attributeValues = messageContent.split(",");

    // !l delete : 로이스 4~7번 슬롯 비우기
    if (messageContent.toLowerCase() === "delete") {
      var cleared = 0;
      for (var j = 3; j < attributeNums.length; j++) {
        for (var i = 0; i < attributeSuffixes.length; i++) {
          var attrName = attributeNums[j] + attributeSuffixes[i];
          var attrObj = findObjs({ _type: "attribute", _characterid: character.id, name: attrName })[0];
          if (attrObj) {
            attrObj.set("current", "");
            cleared++;
          }
        }
      }
      var characterName = character.get("name") || msg.who || "???";
      sendChat("System", characterName + "의 시나리오 로이스를 비웠습니다.", null, {noarchive: false});
      return;
    }

    if (attributeValues.length < 5) {
      sendChat("API", "Error: 입력값 부족.");
      return;
    }

    var jetztlois = [attributeValues[0], attributeValues[1], attributeValues[2], attributeValues[3], attributeValues[4]];
    var nextNumIndex = 0;

    for (var i = 0; i < attributeValues.length; i++) {
      var attributeName = "";
      var attributeValue = attributeValues[i] ? attributeValues[i].trim() : "";
      if (attributeValue === "") {
        sendChat("API", "Error:" + attributeName + "를 못찾음.");
        return;
      }

      for (var j = nextNumIndex; j < attributeNums.length; j++) {
        attributeName = attributeNums[j] + attributeSuffixes[i];
        var attributeObj = findObjs({ _type: "attribute", _characterid: character.id, name: attributeName })[0];

        if (!attributeObj || attributeObj.get("current") === "") {
          if (!attributeObj) {
            attributeObj = createObj("attribute", { _characterid: character.id, name: attributeName, current: attributeValue });
          } else {
            attributeObj.set("current", attributeValue);
          }
          nextNumIndex = j;
          break;
        }
      }

      if (nextNumIndex === attributeNums.length) {
        sendChat("API", "Error: 머임");
        return;
      }
    }

    // 채팅 메시지 전송 
    const loiStyle = "padding: 8px 12px; font-weight: bold; line-height: 170%; background: #D0CCFF; display: inline-block; text-decoration: none;";
    try {
      var chat_id = "character|" + character.id;
      var characterName = character.get("name") || msg.who || "???";
      sendChat(chat_id, "[img](https://files.d20.io/images/390944966/OJhlJm_H_qemso_rOa2NKA/med.png)", null, {noarchive: false});
      sendChat(chat_id, "<span style='" + loiStyle + "'>" +
        "【"+characterName + "】" +
        "<br>" +
        jetztlois[1] + "에게 관계: " +
        jetztlois[0] + " P감정: " +
        jetztlois[2] + " N감정: " +
        jetztlois[3] + "중 " +
        jetztlois[4] + "으로 로이스 취득" +
        "</span>", null, {noarchive: false});
    } catch (err) {
      sendChat('error', '/w GM ' + err, null, {noarchive: true});
    }
  }
});


* LOIS API(타텍님)

on("chat:message", function(msg) {
  if (msg.type == "api" && (msg.content.indexOf("!l ") === 0 || msg.content.indexOf("!로 ") === 0)) {
    var character = findObjs({_type: "character", name: msg.who})[0];
    if (!character) {
      sendChat("API", "Error: 캐릭터를 찾을 수 없습니다.");
      return;
    }

    var attributeNums = ["lois1_", "lois2_", "lois3_", "lois4_", "lois5_", "lois6_", "lois7_"];
    var attributeSuffixes = ["rel", "tit", "emot_good", "emot_bad", "desc"];
    var messageContent = msg.content.split(" ").slice(1).join(" ").trim();
    var attributeValues = messageContent.split(",");

    // !l delete : 로이스 4~7번 슬롯 비우기
    if (messageContent.toLowerCase() === "delete") {
      var cleared = 0;
      for (var j = 3; j < attributeNums.length; j++) {
        for (var i = 0; i < attributeSuffixes.length; i++) {
          var attrName = attributeNums[j] + attributeSuffixes[i];
          var attrObj = findObjs({ _type: "attribute", _characterid: character.id, name: attrName })[0];
          if (attrObj) {
            attrObj.set("current", "");
            cleared++;
          }
        }
      }
      var characterName = character.get("name") || msg.who || "???";
      sendChat("System", characterName + "의 시나리오 로이스를 비웠습니다.", null, {noarchive: false});
      return;
    }

    if (attributeValues.length < 5) {
      sendChat("API", "Error: 입력값 부족.");
      return;
    }

    var jetztlois = [attributeValues[0], attributeValues[1], attributeValues[2], attributeValues[3], attributeValues[4]];
    var nextNumIndex = 0;

    for (var i = 0; i < attributeValues.length; i++) {
      var attributeName = "";
      var attributeValue = attributeValues[i] ? attributeValues[i].trim() : "";
      if (attributeValue === "") {
        sendChat("API", "Error:" + attributeName + "를 못찾음.");
        return;
      }

      for (var j = nextNumIndex; j < attributeNums.length; j++) {
        attributeName = attributeNums[j] + attributeSuffixes[i];
        var attributeObj = findObjs({ _type: "attribute", _characterid: character.id, name: attributeName })[0];

        if (!attributeObj || attributeObj.get("current") === "") {
          if (!attributeObj) {
            attributeObj = createObj("attribute", { _characterid: character.id, name: attributeName, current: attributeValue });
          } else {
            attributeObj.set("current", attributeValue);
          }
          nextNumIndex = j;
          break;
        }
      }

      if (nextNumIndex === attributeNums.length) {
        sendChat("API", "Error: 머임");
        return;
      }
    }

    // 채팅 메시지 전송 
    const loiStyle = "padding: 8px 12px; font-weight: bold; line-height: 170%; background: #D0CCFF; display: inline-block; text-decoration: none;";
    try {
      var chat_id = "character|" + character.id;
      var characterName = character.get("name") || msg.who || "???";
      sendChat(chat_id, "[img](https://files.d20.io/images/390944966/OJhlJm_H_qemso_rOa2NKA/med.png)", null, {noarchive: false});
      sendChat(chat_id, "<span style='" + loiStyle + "'>" +
        "【"+characterName + "】" +
        "<br>" +
        jetztlois[1] + "에게 관계: " +
        jetztlois[0] + " P감정: " +
        jetztlois[2] + " N감정: " +
        jetztlois[3] + "중 " +
        jetztlois[4] + "으로 로이스 취득" +
        "</span>", null, {noarchive: false});
    } catch (err) {
      sendChat('error', '/w GM ' + err, null, {noarchive: true});
    }
  }
});




※ 간단 수정

    1) 배경색인 연보라색은 # D0CCFF 를 찾아서 HEX 코드만 바꿔주세요. 

    2) 로이스 등록 이미지를 삭제하시고 싶은 분은 코드에서 형광 노란색 바탕이 걸린 부분만 지워주세요.


*매크로

!l ?{관계명| }, ?{취득 대상| }, ?{P감정|감정1|감정2|감정3|감정4|감정5|감정6|감정7|감정8|감정9|감정10|감정11|감정12|감정13|감정14|감정15|감정16|감정17|감정18|감정19|감정20|감정21|감정22|임의}, ?{N감정|감정1|감정2|감정3|감정4|감정5|감정6|감정7|감정8|감정9|감정10|감정11|감정12|감정13|감정14|감정15|감정16|감정17|감정18|감정19|감정20|감정21|감정22|임의}, ?{감정|P감정|N감정}

※ 룰북의 내용을 적는 것이라 감정 1,2,3…으로 적어두었습니다. 코어 룰북 1권 P.298/299를 보고 감정을 직접 적어주세요.


즐거운 덥크 즐기시길!