Here is the code for any of the scripter writers to review and understand what he/she did and maybe improve on it skip the first several lines that are in korean but the code itself is not. /* <a href="https://github.com/kibkibe/roll20-api-scripts/tree/master/visual_dialogue" rel="nofollow">https://github.com/kibkibe/roll20-api-scripts/tree/master/visual_dialogue</a> */
/* (visual_dialogue.js) 220118 코드 시작 */
// define: global constant
state.api_tag = "<a href=\"#vd-permitted-api-chat\"></a>";
state.vd_divider = "ℍ";
state.last_displayed_time = 0;
// /define: global constant
// define: option
const vd_setting = {
// option: 한 화면에 표시할 수 있는 스탠딩 이미지의 최대 개수를 설정합니다.
// 이 숫자를 넘어가면 엑스트라, 혹은 채팅기록이 가장 오래된 캐릭터의 스탠딩이 삭제되고 그 위치에 새 스탠딩이 추가됩니다.
max_number: 5,
// option: 표시할 스탠딩 이미지들의 가로 사이즈입니다.
width: 420,
// option: 표시할 스탠딩 이미지들의 세로 사이즈입니다.
height: 420,
// option: 스탠딩 이미지의 가로 너비 중 화면 밖으로 빠져나가지 않도록 보장할 가로 사이즈입니다.
fit_width: 200,
// option: 캐릭터들이 여러 감정표현을 사용할지(true) 대표 스탠딩 하나만 사용할지(false) 설정합니다.
// false일 경우 deck_name에 설정한 카드 덱에서 모든 캐릭터의 스탠딩 이미지를 가져옵니다.
use_emotion: true,
// option: /as를 이용해 저널에 없는 캐릭터로 채팅할 경우 엑스트라 전용 스탠딩을 표시할지 (true) 스탠딩을 생략할지(false) 설정합니다.
// true일 경우 extra_name에 설정한 이름에 따라 엑스트라용 스탠딩을 가져옵니다.
show_extra_standing: true,
// option: use_emotion가 false일 경우에 캐릭터의 스탠딩 이미지를 가져올 카드덱의 이름을 설정합니다.
deck_name: "standings",
// option: use_emotion이 true일 경우에 엑스트라용 스탠딩 이미지를 가져올 카드덱의 이름을 설정합니다.
extra_name: "extra",
// option: show_extra_standing 옵션과 별개로 스탠딩을 표시하지 않을 캐릭터의 이름을 기입합니다. 여러개일 경우 콤마(,)로 구분합니다.
ignore_list: "GM",
// option: 스크립트를 사용할 페이지의 이름을 지정합니다. 여러개일 경우 콤마(,)로 구분합니다.
// 페이지가 여럿일 경우 Player 북마크가 설정된 페이지에 우선적으로 대사가 표시됩니다.
page_list: "conversation,intro",
// option: 캐릭터의 이름이 표시되는 텍스트 박스의 폰트 사이즈를 설정합니다.
name_font_size: 18,
// option: 캐릭터 이름의 글씨색을 설정합니다.
name_font_color: "rgb(255, 255, 255)",
// option: 대사 내용이 표시되는 텍스트 박스의 폰트 사이즈를 설정합니다.
dialogue_font_size: 16,
// option: 대사 내용의 글씨색을 설정합니다.
dialogue_font_color: "rgb(255, 255, 255)",
// option: /desc나 /em으로 표시되는 강조된 텍스트 박스의 폰트 사이즈를 설정합니다.
desc_font_size: 20,
// option: /desc, /em의 글씨색을 설정합니다.
desc_font_color: "rgb(255, 255, 255)",
// option: 한번에 여러 채팅이 몰려서 순차적으로 표시해야 할 경우 채팅당 최소 노출시간을 설정합니다. (1000=1초)
min_showtime: 1000,
// option: 채팅 1글자당 표시 시간. 숫자가 커질수록 글자수 대비 대사의 표시시간이 길어집니다.
showtime_ratio: 20,
// option: (고급설정) 각 열이 간격이 font_size 대비 얼마만큼의 픽셀을 차지하는지의 비율을 지정합니다.
line_height: 1.7,
// option: (고급설정) 각 글자가 font_size 대비 얼마만큼의 픽셀을 차지하는지의 비율을 지정합니다.
letter_spacing: 0.85
};
// /define: option
on('ready', function() {
// on.ready
state.vd_stock = [];
// /on.ready
on("add:card", function(obj) {
updateMacro(obj);
});
});
on("change:card", function(obj,prev) {
// on.change:card
updateMacro(obj);
// /on.change:card
});
on("destroy:card", function(obj) {
// on.destroy:card
updateMacro(obj);
// /on.destroy:card
});
on("destroy:graphic", function(obj) {
// on.destroy:graphic
if (obj.get('name') == "vd_standing") {
arrangeStandings(false);
}
// /on.destroy:graphic
});
on("chat:message", function(msg)
{
// on.chat:message
if ((msg.type == "general" || msg.type == "desc" || msg.type == "emote")
&& (msg.playerid != 'API' || msg.content.includes(state.api_tag))
&& !msg.rolltemplate){
if (findCharacterWithName(msg.who) || findObjs({_type:'player',_displayname:msg.who.replace(' (GM)','')}).length == 0) {
if (msg.content.length > 0) {
msg.content = msg.content.replace(state.api_tag,'').replace(/<br>/g,state.vd_divider);
msg.time = new Date().getTime();
state.vd_stock.push(msg);
if (state.vd_stock.length == 1 || (state.vd_stock.length > 1 && state.last_displayed_time + 5000 < msg.time)) {
setTimeout(showDialogue, 100);
}
}
}
}
// /on.chat:message
// on.chat:message:api
if (msg.type == "api" && msg.content.indexOf("!@") === 0) {
const current_page_id = vdGetCurrentPage();
if (!current_page_id) {
return;
}
if (msg.content == '!@숨김' || msg.content == '!@hide') {
showHideDecorations('vd_deco',false);
showHideDecorations('vd_panel',false);
let bg_name = findObjs({ _type: 'graphic', name:'vd_name', _pageid:current_page_id});
let bg_dialogue = findObjs({ _type: 'graphic', name:'vd_dialogue', _pageid:current_page_id});
if (bg_name.length > 0) {
bg_name = bg_name[0];
} else {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_name 토큰이 없습니다.",null,{noarchive:true});
return;
}
if (bg_dialogue.length > 0) {
bg_dialogue = bg_dialogue[0];
} else {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_dialogue 토큰이 없습니다.",null,{noarchive:true});
return;
}
let text_name = getObj('text', bg_name.get('gmnotes'));
let text_dialogue = getObj('text', bg_dialogue.get('gmnotes'));
text_name.remove();
text_dialogue.remove();
sendChat('vd-api-wildcard','!@퇴장:전원');
} else if (msg.content.indexOf("!@퇴장") == 0 || msg.content.indexOf("!@exit") == 0) {
const keyword = msg.content.replace("!@퇴장","").replace("!@exit","").replace(state.api_tag,'');
if (keyword.length == 0) {
removeStanding(msg);
} else if (playerIsGM(msg.playerid) || msg.playerid == 'API' || msg.who == 'vd-api-wildcard') {
if (keyword == ":전원" || keyword == ":전체" || keyword == ":all") {
let tokens = findObjs({ _type: 'graphic', name: 'vd_standing', _pageid: current_page_id});
tokens.forEach(token => {
token.remove();
});
} else if (keyword == ":엑스트라" || keyword == ":extra") {
let tokens = findObjs({ _type: 'graphic', name: 'vd_standing', represents: '', _pageid: current_page_id});
tokens.forEach(token => {
token.remove();
});
} else {
msg.who = keyword.replace(":","");
removeStanding(msg);
}
}
} else if (playerIsGM(msg.playerid) && (msg.content == "!@리셋" || msg.content == "!@reset")) {
state.vd_stock = [];
sendChat("error","/w gm 표시 대기열에 쌓여있던 대사들을 초기화했습니다.",null,{noarchive:true});
} else if (playerIsGM(msg.playerid) && (msg.content == "!@강제진행" || msg.content == "!@force-progress")) {
showNextDialogue();
} else if (playerIsGM(msg.playerid) && (msg.content.indexOf("!@배경 ") == 0 || msg.content.indexOf("!@background ") == 0)) {
let bg_background = findObjs({ _type: 'graphic', name:'vd_background', _pageid:current_page_id});
let bg_deck = findObjs({_type: 'deck', name:'background'});
if (bg_background.length == 0) {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_background 토큰이 없습니다.",null,{noarchive:true});
return;
}
bg_background = bg_background[0];
const current_url = bg_background.get('imgsrc');
const new_bg = msg.content.replace('!@배경 ','').replace('!@background ','');
let is_url_input = (msg.content.indexOf('https://') > -1);
if (is_url_input) {
bg_background.set('imgsrc',new_bg.replace('med','thumb').replace('max','thumb').replace(' ',''));
if (current_url == bg_background.get('imgsrc')) {
sendChat("error","/w gm 배경 이미지가 정상적으로 변경되지 않았습니다. 주소나 명령어 형식이 올바른지 확인해주세요. (ex: !@배경 https://이미지주소...)",null,{noarchive:true});
}
} else {
if (bg_deck.length == 0) {
sendChat("error","/w gm **background** 덱이 콜렉션에 없습니다. 사용할 배경 이미지를 background 덱에 넣거나 !@배경 https://이미지주소... 형식으로 이미지 주소를 직접 입력하세요.",null,{noarchive:true});
return;
} else {
let bg_cards = findObjs({_type:'card', _deckid: bg_deck[0].get('_id'), name: new_bg});
if (bg_cards.length == 0) {
sendChat("error","/w gm 이름이 '**" + new_bg + "'**인 배경 카드가 **background** 덱에 없습니다.",
null,{noarchive:true});
return;
} else {
bg_background.set('imgsrc',bg_cards[0].get('avatar').replace('med','thumb').replace('max','thumb'));
}
}
}
} else if (vd_setting.use_emotion) {
let cha_name = msg.who;
let content_str = msg.content.replace(state.api_tag,'').replace('!@','');
let emot = content_str;
if (content_str.lastIndexOf(':') > -1 && (playerIsGM(msg.playerid) || msg.playerid == 'API')) {
cha_name = content_str.substring(0,content_str.lastIndexOf(':'));
emot = content_str.substring(content_str.lastIndexOf(':')+1,content_str.length);
}
let chat_cha = findCharacterWithName(cha_name);
let current_token = null;
if (chat_cha || vd_setting.show_extra_standing) {
current_token = findTokenWithCharacter(chat_cha?chat_cha.get('_id'):'', cha_name);
}
if (current_token) {
let rt = findObjs({ _type: 'deck', name: vd_setting.deck_name});
if (rt.length == 0) {
sendChat("error","/w gm 이름이 **" + vd_setting.deck_name +"**인 카드 덱이 없습니다.",null,{noarchive:true});
return;
} else {
let opt = {
name: 'vd_standing',
_subtype: 'card',
_pageid: current_token.get('_pageid'),
width: vd_setting.width,
height: vd_setting.height,
bar1_value: cha_name,
left:current_token.get('left'),
top:current_token.get('top'),
layer: 'map',
represents: chat_cha? chat_cha.get('_id'): '',
tint_color: current_token.get('tint_color')
};
let search_opt = { _type: 'card', _deckid: rt[0].get('_id')};
if (!vd_setting.use_emotion) {
search_opt.name = chat_cha ? cha_name : vd_setting.extra_name;
} else if (emot.length > 0) {
search_opt.name = cha_name + "-" + emot;
} else {
search_opt.name = cha_name;
}
let rt_items = findObjs(search_opt);
if (rt_items.length > 0) {
opt.imgsrc = rt_items[0].get('avatar').replace('med','thumb').replace('max','thumb');
} else {
sendChat("error","/w gm **"+ vd_setting.deck_name + "** 카드 덱에 이름이 **" + search_opt.name + "**인 카드가 없습니다.",null,{noarchive:true});
return;
}
current_token.set(opt);
}
} else {
sendChat("error","/w \"" + msg.who + "\" 이름이 **" + cha_name +"**인 캐릭터가 없습니다.",null,{noarchive:true});
}
}
}
// /on.chat:message:api
});
// define: global function
const vdGetCurrentPage = function() {
const page_list = vd_setting.page_list.replace(/, /g,',').replace(/ ,/g,',').split(',');
if (page_list.indexOf(getObj('page',Campaign().get("playerpageid")).get('name')) > -1) {
return Campaign().get("playerpageid");
} else {
const page = findObjs({type:'page',name:page_list[0]});
if (page.length > 0) {
return page[0].get('_id');
} else {
sendChat("error","/w gm 이름이 **" + page_list[0] + "**인 페이지가 없습니다.",null,{noarchive:true});
}
}
}
const showDialogue = function() {
let msg = state.vd_stock[0];
for (let index = 1; index < state.vd_stock.length; index++) {
const element = state.vd_stock[index];
if (element.who == msg.who && Math.abs(element.time - msg.time) < 100) {
msg.content = msg.content + state.vd_divider + element.content;
state.vd_stock.splice(index,1);
index--;
} else {
break;
}
}
const current_page_id = vdGetCurrentPage();
if (!current_page_id) {
return;
}
let is_general = msg.type == "general";
const font_color = vd_setting[is_general?'dialogue_font_color':'desc_font_color'];
let font_size = vd_setting[is_general ? 'dialogue_font_size' : 'desc_font_size'];
let bg_area = findObjs({ _type: 'graphic', name:'vd_area', _pageid:current_page_id});
let bg_name = findObjs({ _type: 'graphic', name:'vd_name', _pageid:current_page_id});
let bg_dialogue = findObjs({ _type: 'graphic', name:'vd_dialogue', _pageid:current_page_id});
let bg_panel = findObjs({ _type: 'graphic', name:'vd_panel', _pageid:current_page_id});
let split = [];
if (bg_area.length > 0) {
bg_area = bg_area[0];
} else {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_area 토큰이 없습니다.",null,{noarchive:true});
showNextDialogue();
return;
}
if (bg_name.length > 0) {
bg_name = bg_name[0];
} else {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_name 토큰이 없습니다.",null,{noarchive:true});
showNextDialogue();
return;
}
if (bg_dialogue.length > 0) {
bg_dialogue = bg_dialogue[0];
} else {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_dialogue 토큰이 없습니다.",null,{noarchive:true});
showNextDialogue();
return;
}
if (bg_panel.length > 0) {
bg_panel = bg_panel[0];
} else {
sendChat("error","/w gm **" + getObj('page',current_page_id).get('name') + "** 페이지에 vd_panel 토큰이 없습니다.",null,{noarchive:true});
showNextDialogue();
return;
}
const width = bg_dialogue.get('width');
const name_width = bg_name.get('width');
let blank_name = '';
let blank_dialogue = '';
let text_name = getObj('text', bg_name.get('gmnotes'));
let text_dialogue = getObj('text', bg_dialogue.get('gmnotes'));
while (name_width > blank_name.length*vd_setting['name_font_size']*vd_setting['letter_spacing']*1.2) { blank_name += " "; }
while (width>blank_dialogue.length*font_size*vd_setting['letter_spacing']*1.15) { blank_dialogue += " "; }
if (text_name && text_name.get('_pageid') != current_page_id) {
text_name.remove();
text_name = null;
}
if (text_dialogue && text_dialogue.get('_pageid') != current_page_id) {
text_dialogue.remove();
text_dialogue = null;
}
if (!text_name) {
text_name = createObj('text', {
_pageid: bg_area.get('_pageid'),
left: bg_name.get('left'),
top: bg_name.get('top'),
width: bg_name.get('width'),
height: bg_name.get('height'),
layer: 'objects',
font_family: 'Arial',
text: '',
font_size: vd_setting['name_font_size'],
color: vd_setting['name_font_color']
});
bg_name.set({'gmnotes':text_name.get('_id')});
}
if (!text_dialogue) {
text_dialogue = createObj('text', {
_pageid: bg_dialogue.get('_pageid'),
left: bg_dialogue.get('left'),
top: bg_dialogue.get('top'),
width: width,
height: bg_dialogue.get('height'),
layer: 'objects',
font_family: 'Arial',
text: '',
font_size: font_size,
color: font_color
});
bg_dialogue.set({'gmnotes':text_dialogue.get('_id')});
}
// 예외처리할 텍스트 제외
let name = msg.who + '\n' + blank_name;
let filtered = msg.content;
let filter_word = [
{regex:/\*.+\*/g,replace:/\*/g}, // *, **, ***
{regex:/``.+``/g,replace:/``/g}, // ``
{regex:/\[[^\(\)\[\]]*\]\(http[^\(\)\[\]]+\)/g,replace:/\[[^\(\)\[\]]*\]\(http[^\(\)\[\]]+\)/g}, // [](http...)
{regex:/<[^>]*>/g,replace:/<[^>]*>/g}, // <html>
{regex:/\(.{1}\" style=\"[^\)]+\)/g,replace:/\((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*\)/g}, // [](#" style="...)
{regex:/\$\[\[.+\]\]/g,replace:/\$\[\[.+\]\]/g}]; // [[]]
for (let i=0;i<filter_word.length;i++) {
let match = filtered.match(filter_word[i].regex);
if (match) {
for (let j=0;j<match.length;j++) {
filtered = filtered.replace(match[j], match[j].replace(filter_word[i].replace,''));
}
}
}
let ruby_match = filtered.match(/\([^\(\)\[\]]+\)\[[^\(\)\[\]]*\]/g);
if (ruby_match) {
for (let j=0;j<ruby_match.length;j++) {
let rubystr_split = ruby_match[j].substring(1,ruby_match[j].length-1).split(')[');
filtered = filtered.replace(ruby_match[j], rubystr_split[1]+"("+rubystr_split[0]+")");
}
}
if (filtered.length == 0){
showNextDialogue();
return;
}
let str = filtered;
let desc_ratio = is_general ? 1 : 0.8;
let amount = Math.ceil(width/font_size/vd_setting['letter_spacing']*3) -3;
let idx = 0;
let length = 0;
const thirdchar = ['\'',' ',',','.','!',':',';','"'];
const halfchar = ['[',']','(',')','*','^','-','~','<','>','+','l','i','1'];
const arr = thirdchar.concat(halfchar);
let divided = false;
for (let i=0;i<str.length;i++){
let c = str[i];
length += 3;
for (let j=0;j<arr.length;j++) {
if (c==arr[j]) {
length -= (j<thirdchar.length ? 2 : 1);
break;
}
}
if (length >= amount * desc_ratio || c == state.vd_divider) {
let substr = str.substring(idx,i+1).replace(state.vd_divider,'');
split.push(is_general || msg.who.length > 0 ? substr:getStringWithMargin(amount,length,desc_ratio,substr));
idx = i+1;
length = 0;
if ((split.length+1) * font_size * vd_setting['letter_spacing']*3 > bg_dialogue.get('height')) {
state.vd_stock.splice(1,0,{content:filtered.substring(idx,str.length),time:msg.time,playerid:msg.playerid,type:msg.type,who:msg.who});
divided = true;
break;
}
}
}
if (idx < str.length && !divided) {
let substr = str.substring(idx,str.length);
split.push(is_general || msg.who.length > 0 ? substr:getStringWithMargin(amount,length,desc_ratio,substr));
}
if (is_general || msg.who.length > 0) {
while ((split.length+1) * font_size * vd_setting['line_height'] < bg_dialogue.get('height')) {
split.push(' ');
}
} else {
split.splice(0,0,' ');
}
split.push(blank_dialogue);
text_name.set({text:name,left:bg_name.get('left'),font_size: vd_setting['name_font_size'],color: vd_setting['name_font_color'],
top:bg_name.get('top')+vd_setting['name_font_size']*vd_setting['line_height']/2});
text_dialogue.set({text:split.join('\n'),font_size: font_size, color: font_color,
left: msg.type == 'desc' ? bg_panel.get('left'):bg_dialogue.get('left'),
top: msg.type == 'desc'? bg_panel.get('top'):bg_dialogue.get('top')});
toFront(text_name);
toFront(text_dialogue);
setTimeout(() => {
showHideDecorations('vd_panel',true);
showHideDecorations('vd_deco',msg.type != 'desc');
toFront(text_name);
toFront(text_dialogue);
}, 100);
clearTextWithout(text_name, text_dialogue);
const ignore_list = vd_setting.ignore_list.replace(/, /g,',').replace(/ ,/g,',').split(',');
if (msg.type != "desc" && ignore_list.indexOf(msg.who) < 0) {
let chat_cha = findCharacterWithName(msg.who);
let current_token = null;
if (chat_cha || vd_setting.show_extra_standing) {
current_token = findTokenWithCharacter(chat_cha?chat_cha.get('_id'):'', msg.who);
}
let tokens = findObjs({ _type: 'graphic', name: 'vd_standing', _pageid: current_page_id});
let lowest_priority = tokens[0];
for (var i=0;i<tokens.length;i++) {
var token = tokens[i];
token.set('tint_color','#000000');
if (parseInt(token.get('gmnotes')) < parseInt(lowest_priority.get('gmnotes'))) {
lowest_priority = token;
}
}
if (current_token == null && (chat_cha || vd_setting.show_extra_standing)) {
let nm = chat_cha?msg.who:vd_setting.extra_name;
let rt = findObjs({ _type: 'deck', name: vd_setting.deck_name});
if (rt.length == 0) {
sendChat("error","/w gm 이름이 **"+ vd_setting.deck_name + "**인 카드 덱이 없습니다.",null,{noarchive:true});
showNextDialogue();
return;
} else {
let opt = {
name: 'vd_standing',
_pageid: bg_area.get('_pageid'),
width: vd_setting.width,
height: vd_setting.height,
bar1_value: msg.who,
layer: 'gmlayer',
imgsrc: rt[0].get('avatar').replace('med','thumb').replace('max','thumb'),
represents: chat_cha? chat_cha.get('_id'): ''
};
const std = findObjs({ _type: 'card', _deckid: rt[0].get('_id'), name: msg.who});
if (std.length > 0) {
opt.imgsrc = std[0].get('avatar').replace('med','thumb').replace('max','thumb');
}
if (tokens.length >= vd_setting.max_number) {
opt.left = lowest_priority.get('left');
} else {
opt.left = arrangeStandings(true);
}
opt.top = bg_area.get('top');
if (tokens.length >= vd_setting.max_number) {
lowest_priority.set(opt);
current_token = lowest_priority;
} else {
current_token = createObj('graphic', opt);
}
toFront(current_token);
setTimeout(() => {
current_token.set({tint_color:'transparent',gmnotes:Date.now(),layer:"map"});
}, 100);
}
} else if (current_token) {
toFront(current_token);
current_token.set({tint_color:'transparent',gmnotes:Date.now()});
}
}
state.last_displayed_time = new Date().getTime();
setTimeout(showNextDialogue, Math.max(vd_setting.min_showtime, str.length * vd_setting.showtime_ratio));
}
const clearTextWithout = function(name_txt, dial_txt) {
let filtered_txt = filterObjs(function(obj) {
return (obj.get('_type') == 'text' && obj.get("_pageid") == name_txt.get('_pageid')
&& obj.get("_id") != name_txt.get('_id') && obj.get("_id") != dial_txt.get('_id')
&& ((Math.abs(obj.get("left") - name_txt.get('left')) < 100 && Math.abs(obj.get("top") - name_txt.get('top')) < 100) ||
(Math.abs(obj.get("left") - dial_txt.get('left')) < 100 && Math.abs(obj.get("top") - dial_txt.get('top')) < 100)));
});
filtered_txt.forEach(txt => {
txt.remove();
});
}
const updateMacro = function(obj) {
let background_deck = findObjs({type:'deck',name:'background'});
if (background_deck.length > 0 && obj.get('deckid') == background_deck[0].get('id')) {
if (background_deck.length > 1) {
sendChat("error","/w gm **background** 덱이 **" + background_deck.length + "**개 있습니다. 먼저 생성된 1개 덱을 기준으로 매크로를 생성합니다.",null,{noarchive:true});
}
let players = findObjs({type:'player'});
let bg_images = findObjs({_type:'card',_deckid:background_deck[0].get('id')});
let bg_macro = findObjs({_type:'macro',name:"배경전환"});
let action_str = "!@배경 ?{배경을 선택하세요";
let gm_list = "";
for (let index = 0; index < bg_images.length; index++) {
const card = bg_images[index];
action_str += "|" + card.get('name');
}
for (let index = 0; index < players.length; index++) {
const player = players[index];
if (playerIsGM(player.id)) {
gm_list += player.id + ",";
}
}
gm_list = gm_list.substring(0,gm_list.length - 1);
action_str += "}";
let options = {name:"배경전환",action:action_str,visibleto:gm_list};
if (bg_macro.length > 0) {
bg_macro = bg_macro[0];
bg_macro.set(options);
} else {
options.playerid = players[0].get('id');
bg_macro = createObj('macro',options);
}
}
}
const showHideDecorations = function(name, show) {
const text_deco = findObjs({name: name, _type: 'graphic'});
for (let index = 0; index < text_deco.length; index++) {
const itm = text_deco[index];
if (show) {
if (itm.get('gmnotes').includes('/')) {
const wh = itm.get('gmnotes').split('/');
itm.set({width:parseInt(wh[0]),height:parseInt(wh[1]),layer:'objects'});
}
toFront(itm);
} else {
if (itm.get('gmnotes').length == 0) {
itm.set({gmnotes:itm.get('width')+"/"+itm.get('height')});
}
itm.set({width:1,height:1,layer:'gmlayer'});
}
}
}
const showNextDialogue = function() {
state.vd_stock.splice(0,1);
if (state.vd_stock.length > 0) {
showDialogue();
}
}
const getStringWithMargin = function(amount, length, ratio, str) {
let margin = Math.round((amount - length)/4*ratio);
for (var j=0;j<margin;j++){
str = "ㅤ" + str + "ㅤ";
}
return str;
}
const findCharacterWithName = function(who) {
let chat_cha = findObjs({ _type: 'character', name: who});
if (chat_cha.length > 0) {
return chat_cha[0];
} else {
return null;
}
}
const findTokenWithCharacter = function(id, who) {
let arr = findObjs({ _type: 'graphic', name: 'vd_standing', represents: id, _pageid: vdGetCurrentPage(), bar1_value: who});
if (arr.length > 0) {
return arr[0];
}
return null;
}
const removeStanding = function(msg) {
let character = findCharacterWithName(msg.who);
let token = findTokenWithCharacter(character?character.get('_id'):'', msg.who);
if (token) {
token.remove();
arrangeStandings(false);
}
}
const arrangeStandings = function(addNew) {
const currernt_page_id = vdGetCurrentPage();
let tokens = findObjs({ _type: 'graphic', name: 'vd_standing', _pageid: currernt_page_id});
if (tokens.length > 0 || addNew) {
let bg_area = findObjs({ _type: 'graphic', name:'vd_area', _pageid:currernt_page_id});
if (bg_area.length > 0) {
bg_area = bg_area[0];
} else {
sendChat("error1","/w gm vd_area 토큰이 없습니다.",null,{noarchive:true});
return;
}
let tokens_position = [];
const compare = function( a, b ) {
if ( a.left < b.left ){
return -1;
}
if ( a.left > b.left ){
return 1;
}
return 0;
}
for (var i=0;i<tokens.length;i++) {
tokens_position.push({idx:i,left:tokens[i].get('left')});
}
tokens_position.sort(compare);
let final_count = tokens.length + (addNew?1:0);
final_count = final_count<2? 2: final_count;
let space = Math.floor(bg_area.get('width')/final_count);
let left = bg_area.get('left') - Math.floor(bg_area.get('width')/2);
if (space < vd_setting.fit_width) {
left += vd_setting.fit_width/2;
space = Math.floor((bg_area.get('width')-vd_setting.fit_width)/(final_count-1));
} else {
left += space/2
}
let rand = addNew ? Math.floor(Math.random()*(tokens_position.length-1))+1 : Infinity;
rand = rand < 0 ? 0 : rand;
for (var i=0;i<tokens_position.length;i++) {
let token = tokens[tokens_position[i].idx];
token.set('left',left + space * (i + (i>=rand?1:0)));
}
return addNew ? left + space * (rand == Infinity ? 0 : rand) : false;
}
}
// /define: global function
/* (visual_dialogue.js) 220118 코드 종료 */