Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Emojibubble script

In Regards to the emojibubble api script. When installing from the roll20 library into a fresh room i get this:
1622084704
The Aaron
Roll20 Production Team
API Scripter
There's a bug.&nbsp; I'll submit a patch.&nbsp; In the meantime, use this version: var emojibubble = emojibubble||(function(){ var DEFAULTPLAYER; var calculatetokenoffset = function(token){ let width = token.get("width"), height=token.get("height"); let pathX = (width/2); let pathY = ((height/2) -10)*-1; let textX = pathX; let textY = pathY - 4; return { bubbleoffsetx:pathX, bubbleoffsety:pathY, textoffsetx:textX, textoffsety:textY }; } var emojibubbleregister = function(token,emoji){ try{ let ser = state.emojibubble.registry; let tokenid = token.id; if(emoji === "clearemojibubble"){ return emojibubblecleanup(tokenid); } if(ser[tokenid]){ emojibubblecleanup(tokenid); } let emojiBubbleObj = createEmojiBubble(token, emoji); ser[tokenid] = emojiBubbleObj; }catch(err){ } } var emojibubblecheck = function(token){ let tokenid = token.id, ser = state.emojibubble.registry; if(ser[tokenid]){ let x = token.get("left"); let y = token.get("top"); let pathObj = getObj("path",ser[tokenid].pathObj); let textObj = getObj("text",ser[tokenid].textObj); let offsets = calculatetokenoffset(token); try{ if(pathObj !== undefined &amp;&amp; textObj !== undefined) pathObj.set({ left: x + offsets.bubbleoffsetx, top: y + offsets.bubbleoffsety }); textObj.set({ left: x + offsets.textoffsetx, top: y + offsets.textoffsety }); }catch(err){ } } } var emojibubblecleanup = function(tokenid){ let ser = state.emojibubble.registry; if(ser[tokenid] &amp;&amp; ser[tokenid].pathObj !== null){ try{ getObj("path",ser[tokenid].pathObj).remove(); getObj("text",ser[tokenid].textObj).remove(); ser[tokenid].pathObj = null; ser[tokenid].textObj = null; }catch(err){ ser[tokenid].pathObj = null; ser[tokenid].textObj = null; } } } var createEmojiBubble = function(token, emoji){ let x = token.get("left"); let y = token.get("top"); let offsets = calculatetokenoffset(token); let pathObj = createObj('path', { pageid: token.get("pageid"), left: x + offsets.bubbleoffsetx, top: y + offsets.bubbleoffsety, width: 35, height: 30, fill: "#ffffff", stroke: "#000000", stroke_width: 1, layer: 'objects', path: JSON.stringify([["M", 10, 20],["Q", 10, 10, 20, 10],["Q", 25, 10, 30, 10],[ "Q", 40, 10, 40, 20],["Q", 40, 30, 30, 30],[ "Q", 25, 30, 20, 30],[ "Q", 15, 30, 8, 34],["Z"]]), controlledby: "emoji" }); let textObj = createObj('text', { pageid: token.get("pageid"), left: x + offsets.textoffsetx, top: y + offsets.textoffsety, width:30, height:30, font_size:12, layer:"objects", text: emojibuilder(emoji) }); toFront(pathObj); toFront(textObj); return {"pathObj":pathObj.get("_id"),"textObj":textObj.get("_id")}; } var emojiObj = { "longrest":[0x1F6CF,0xFE0F], "shortrest":0x1FA91, "rations":0x1F371, "tavern":0x1F37A, "camp":0x26FA, "inn":0x1F3E1, "nature":[0x1F3DE,0xFE0F], "meal":[0x1F37D,0xFE0F], "meat":0x1F356, "herb":0x1F33F, "hurt":0x1FA78, "search":0x1F50E, "interrogate":[0x1F441,0xFE0F,0x200D,0x1F5E8,0xFE0F], "investigate":[0x1F9E0], "look":[0x1F441,0xFE0F], "barter":[0x1F3F7,0xFE0F], "gamble":0x1F3B2, "door":0x1F6AA, "key":[0x1F5DD,0xFE0F], "deception":0x1F3AD, "hole":[0x1F573,0xFE0F], "archery":0x1F3F9, "duel":[0x2694,0xFE0F], "tracking":0x1F43E, "craft":0x1F528, "sleep":0x1F4A4, "waiting":0x23F3, "speaking":0x2026, "notes":0x1F3B6, "angry":0x1F4A2, "justice":[0x2696,0xFE0F], "sweat":0x1F4A7, "strange":0x1F773, "demon":0x1F479, "dragon":0x1F432, "scroll":0x1F4DC, "alchemy":[0x2697, 0xFE0F], "arcana":0x2728, "temple":[0x1F3DB,0xFE0F], "castle":0x1F3F0, "dead":[0x26B0,0xFE0F], "death":[0x2620,0xFE0F], "quest":0x2757, "question":0x2753, "no":0x1F44E, "yes":0x1F44D, "gem":0x1F48E, "candle":[0x1F56F,0xFE0F], "cleric":0x1F4FF, "shield":[0x1F6E1,0xFE0F], "owl":0x1F989, "fox":0x1F98A, "bear":0x1F43B, "bull":0x1F42E, "eagle":0x1F414, "cat":0x1F431, "fire":0x1F525, "water":0x1F30A, "earth":[0x26F0,0xFE0F], "wind":0x1F4A8, "heart":[0x2764,0xFE0F], "smile":0x1F642, "hearteyes":0x1F60D, "rofl":0x1F923, "sus":0x1F928, "neutral":0x1F610, "pensive":0x1F614, "sad":0x1F61F, "mad":0x1F620, "cursing":0x1F92C, "sick":0x1F922, "poo":'0x1F4A9' }; var initializeemoji = function(){ log('in initializeemoji'); let emojilibrary = {}, dupArray = function(arr){ let dup = [], iterator=0; _.each(arr,function(a){ dup[iterator++]=a; }) return dup; }; _.each(emojiObj, function(emobj,key){ emojilibrary[key] = (Array.isArray(emobj))?dupArray(emobj):emobj; }); return emojilibrary; } var digestEmoji = function(emoji){ var sliceEmoji = function(str) { let res = ['', '']; for (let c of str) { let n = c.codePointAt(0); let isEmoji = n &gt; 0xfff || n === 0x200d || (0xfe00 &lt;= n &amp;&amp; n &lt;= 0xfeff); res[1 - isEmoji] += c; } return res; } var hex = function(str) { return [...str].map(x =&gt; x.codePointAt(0).toString(16)) } return sliceEmoji(emoji).map(hex)[0]; } var emojibuilder = (numref) =&gt; { let results = "",emoji=state.emojibubble.customemoji[numref]; if(Array.isArray(emoji)){ _.each(emoji, function(ref){ try{ results += emojiFromCodePoint(ref); }catch(err){log(err.message)} }); }else{ try{ results += emojiFromCodePoint(emoji); }catch(err){log(err.message)} } return results; } var emojiFromCodePoint = function(num){ if(typeof num === "string"){ if(num.indexOf("0x") === -1){ num = "0x" + num; } } return String.fromCodePoint(num); } var emojilistHTML = function(){ let list = '',count=0; list += '&lt;p style="width:100%;text-align:center;"&gt;&lt;a style="border: 2px solid black ; font-size: 14pt ; height: 25px ; display: inline-block ; margin: 2px ; border-radius: 5px;padding:5px 2px 0 2px;color:#000;" href="!emojibubble|clearemojibubble"&gt;Clear Emoji Bubble&lt;/a&gt;&lt;/p&gt;'; _.each(state.emojibubble.customemoji, function(emobj,key){ list+= '&lt;a style="border:2px solid black;font-size:14pt;width:25px;height:25px;display:inline-block;margin:2px;border-radius:5px;text-align:center;" title="' + key + '" href="!emojibubble|' + key + '"&gt;' + emojibuilder(key) + '&lt;/a&gt;'; count++; if(count%12===0){ list+='&lt;hr /&gt;'; }else if(count%4===0){ list+='&lt;span style="font-size:14pt;margin:0 8pt 0 8pt;"&gt; | &lt;/span&gt;'; } }); return list; } var buttonstyle = 'border: 2px solid black ; font-size: 14pt ; height: 25px ; display: inline-block ; margin: 2px ; border-radius: 5px;padding:5px 2px 0 2px;color:#000;'; var emojicontrollistHTML = function(){ let list = '',count=0; list += '&lt;p style="width:100%;text-align:center;"&gt;'; list += '&lt;a style="' + buttonstyle + '" href="!emojibubble --addemoji|?{Emoji}|?{Name}"&gt;Add Emoji&lt;/a&gt;'; list += '&lt;a style="' + buttonstyle + '" href="!emojibubble --resetemoji"&gt;Reset Emoji&lt;/a&gt;'; list += '&lt;/p&gt;'; list += '&lt;p&gt;You can find new emoji to copy-paste at &lt;a href="<a href="https://emojipedia.org/&quot;&gt;Emojipedia&lt;/a" rel="nofollow">https://emojipedia.org/"&gt;Emojipedia&lt;/a</a>&gt; among other places.&lt;/p&gt;'; list += '&lt;div style="position:inline-block;width:100%;outline:2px solid white;text-align:center;'+ buttonstyle+'"&gt;Click on Gray Emoji to Delete&lt;/div&gt;'; _.each(state.emojibubble.customemoji, function(emobj,key){ list+= '&lt;a style="border:2px solid black;font-size:14pt;width:25px;height:25px;display:inline-block;margin:2px;border-radius:5px;text-align:center;background-color:#999;" title="' + key + '" href="!emojibubble --deleteemoji|' + key + '"&gt;' + emojibuilder(key) + '&lt;/a&gt;'; count++; if(count%12===0){ list+='&lt;hr /&gt;'; }else if(count%4===0){ list+='&lt;span style="font-size:14pt;margin:0 8pt 0 8pt;"&gt; | &lt;/span&gt;'; } }); return list; } var outputToChat = function(msg,tgt){ if(tgt==="all"){ sendChat("Emojibubble", msg); }else{ tgt = (tgt !== undefined &amp;&amp; tgt !== null)?tgt:"gm"; sendChat("Emojibubble","/w \"" + tgt + "\" " + msg,null,{noarchive:true}); } } var msgHandler = function(msg){ if(msg.type === "api" &amp;&amp; msg.content.indexOf("!emojibubble") === 0 ){ let who = (playerIsGM(msg.playerid))?"gm":msg.who; let emoji = msg.content.split("|")[1]; var token = (msg.selected)?getObj("graphic",msg.selected[0]._id):null; if(msg.content.indexOf("--targettoken") !== -1){token = getObj("graphic",msg.content.split("|")[2]);} if(msg.content.indexOf("--addemoji") !== -1){ if(who==='gm'){ let digested = digestEmoji(emoji); let key = msg.content.split("|")[2]; state.emojibubble.customemoji[key] = digested; rebuildhandoutcontent(); rebuildmacro(); rebuildselecedtmacro(); } return; } if(msg.content.indexOf("--resetemoji") !== -1){ if(who==='gm'){ state.emojibubble.customemoji = initializeemoji(); rebuildhandoutcontent(); rebuildmacro(); rebuildselecedtmacro(); } return; } if(msg.content.indexOf("--deleteemoji")!== -1){ if(who==='gm'){ delete state.emojibubble.customemoji[emoji]; rebuildhandoutcontent(); rebuildmacro(); rebuildselecedtmacro(); } return false; } if(token === null || token === undefined){ return outputToChat("Select a token to say your emoji.",who); } if(token &amp;&amp; token.get("_subtype") !== "token"){ return outputToChat("Select a target that is a token.",who); } if(msg.selected !== undefined){ _.each(msg.selected, function(token_ref){ let token = getObj("graphic",token_ref._id); emojibubbleregister(token,emoji); }); }else{ emojibubbleregister(token,emoji); } } } var rebuildhandoutcontent = function(){ let handout = getObj("handout",state.emojibubble.help), content = helpDisplay(), gmcontent = controlsDisplay(); if(handout){ setTimeout(function(){ //handout.set("notes",content + '&lt;span style="color:white;"&gt;.&lt;/span&gt;'); handout.set("gmnotes", gmcontent);// + '&lt;span style="color:white;"&gt;.&lt;/span&gt;' setTimeout(function(){ handout.set("notes",content); //handout.set("gmnotes", gmcontent); },0); },0); } } var rebuildmacrocontent = function(){ return ('!emojibubble --targettoken|?{' + createEmojiSelect() + '}|@{target|speaker|token_id}'); } var rebuildselectedmacrocontent = function(){ return ('!emojibubble|?{' + createEmojiSelect() + '}'); } var rebuildmacro = function(){ if(state.emojibubble.macro &amp;&amp; getObj("macro",state.emojibubble.macro)){ var macro = getObj("macro",state.emojibubble.macro); macro.set("action",rebuildmacrocontent()); } } var rebuildselecedtmacro = function(){ if(state.emojibubble.selectedmacro &amp;&amp; getObj("macro",state.emojibubble.selectedmacro)){ var macro = getObj("macro",state.emojibubble.selectedmacro); macro.set("action",rebuildselectedmacrocontent()); } } var helpDisplay = function(){ return emojilistHTML(); } var controlsDisplay = function(){ return emojicontrollistHTML(); } var initialize = function(){ on("chat:message", msgHandler); on("change:graphic", emojibubblecheck); DEFAULTPLAYER = (function(){ let player; let playerlist = findObjs({ _type: "player", }); _.each(playerlist, function(obj) { if(playerIsGM(obj.get("_id"))){ player = obj; } }); return player; })(); var helpoutput = ""; if(!state.emojibubble.help || !getObj("handout",state.emojibubble.help)){ if(findObjs({type:"handout",name:"Emojibubble Console"})[0]){ state.emojibubble.help = findObjs({type:"handout",name:"Emojibubble Console"})[0].get("_id"); }else{ let content = helpDisplay(), gmcontent = controlsDisplay(), handout = createObj("handout",{ name: "Emojibubble Console", inplayerjournals: "all" }); state.emojibubble.help = handout.get("_id"); setTimeout(function(){ handout.set("notes",content); setTimeout(function(){ handout.set("gmnotes", gmcontent); },100); },100); } } helpoutput += '&lt;p&gt;' + '&lt;a href="<a href="http://journal.roll20.net/handout/" rel="nofollow">http://journal.roll20.net/handout/</a>' + state.emojibubble.help + '"&gt;Emojibubble Console&lt;/a&gt;&lt;/p&gt;'; createEmojiSelect = function(){ let list = 'Select Emoji',count=0; list += '|Clear,clearemojibubble'; _.each(state.emojibubble.customemoji, function(emobj,key){ list+= '|' + emojibuilder(key) + ',' + key; }); return list; } outputToChat(helpoutput, "all"); if(!state.emojibubble.macro || !getObj("macro",state.emojibubble.macro)){ var macro = findObjs({type:"macro",name:"Target_Emojibubble"})[0]; try{ }catch(err){ log(err.message); } if( macro &amp;&amp; macro !== undefined ){ state.emojibubble.macro = macro.id; }else{ let action =rebuildmacrocontent(), macro = createObj("macro",{ playerid:DEFAULTPLAYER.id, name: "Target_Emojibubble", action: action, visibleto: DEFAULTPLAYER.id, istokenaction:false }); state.emojibubble.macro = macro.id; } } if(!state.emojibubble.selectedmacro || !getObj("macro",state.emojibubble.selectedmacro)){ var macro = findObjs({type:"macro",name:"Selected_Emojibubble"})[0]; try{ }catch(err){ log(err.message); } if( macro &amp;&amp; macro !== undefined ){ state.emojibubble.selectedmacro = macro.id; }else{ let action =rebuildselectedmacrocontent(), macro = createObj("macro",{ playerid:DEFAULTPLAYER.id, name: "Selected_Emojibubble", action: action, visibleto: "all", istokenaction:false }); state.emojibubble.selectedmacro = macro.id; } } } state.emojibubble = state.emojibubble||{}; state.emojibubble.registry = state.emojibubble.registry||{}; state.emojibubble.help = state.emojibubble.help||""; state.emojibubble.customemoji = state.emojibubble.customemoji||initializeemoji(); state.emojibubble.macro = state.emojibubble.macro||""; state.emojibubble.selectedmacro = state.emojibubble.selectedmacro||""; return { create: emojibubbleregister, initialize:initialize } })(); on("ready", function(){ emojibubble.initialize(); });
1622085532
The Aaron
Roll20 Production Team
API Scripter
Pull request with fix:&nbsp;<a href="https://github.com/Roll20/roll20-api-scripts/pull/1295" rel="nofollow">https://github.com/Roll20/roll20-api-scripts/pull/1295</a>
Thank you Aaron! Again I am in your debit.
1622130782
The Aaron
Roll20 Production Team
API Scripter
No problem. =D