-
-
Save shdwjk/9cfdaa7efd4bb3dc55474e372f8f87af to your computer and use it in GitHub Desktop.
Roll20 SpeechBalloon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const SpeechBalloon = (() => { // eslint-disable-line no-unused-vars | |
const version = 0.1; // eslint-disable-line no-unused-vars | |
const schemaVersion = 0.4; | |
const defaultShowLength = 4; // seconds | |
const msPerSec = 1000; // for conversions.. no magic numbers! | |
const checkStepRate = 1000; //ms = 1 second | |
const checkInstall = () => { | |
if( ! _.has(state,'SpeechBalloon') || state.SpeechBalloon.version !== schemaVersion) { | |
log('SpeechBalloon: Resetting state'); | |
/* Default Settings stored in the state. */ | |
state.SpeechBalloon = { | |
version: schemaVersion, | |
pageGraphicMap: {}, | |
queue: [], | |
validUntil: 0, | |
bubbleShown: false | |
}; | |
} | |
if( ! _.has(state,'lastBalloon')) { | |
log('SpeechBalloon.lastBalloon: Resetting state'); | |
/* Default Settings stored in the state. */ | |
state.lastBalloon = false; | |
} | |
setInterval(checkBubbleDisplay,checkStepRate); | |
}; | |
const bustBalloon = (pageObject) => { | |
if(state.SpeechBalloon.bubbleShown) { | |
if (typeof(pageObject) != "undefined") { | |
const page = pageObject.id; | |
const bubbleBorder = state.SpeechBalloon.pageGraphicMap[page].BorderId; | |
const bubbleTail = state.SpeechBalloon.pageGraphicMap[page].TailId; | |
const bubbleFill = state.SpeechBalloon.pageGraphicMap[page].FillId; | |
const bubbleText = state.SpeechBalloon.pageGraphicMap[page].TextId; | |
const hiddenState={ | |
layer: "gmlayer", | |
width: 35, | |
height: 35, | |
left: 35, | |
top: 35 | |
}; | |
getObj("graphic" , bubbleBorder).set( hiddenState ); | |
getObj("graphic" , bubbleTail).set( hiddenState ); | |
getObj("graphic" , bubbleFill).set( hiddenState ); | |
getObj("text" , bubbleText).set( hiddenState ); | |
state.SpeechBalloon.bubbleShown=false; | |
} | |
} | |
}; | |
const checkBubbleDisplay = () => { | |
if(state.SpeechBalloon.validUntil < Date.now() ) { | |
if( state.SpeechBalloon.queue.length == 0 ) { | |
if ( state.lastBalloon != false ) { | |
let page = getObj('page',state.lastBalloon.pageid); | |
if(page){ | |
bustBalloon(page); | |
} | |
state.lastBalloon = false; | |
} | |
} else { | |
let nextBubble=state.SpeechBalloon.queue.shift(); | |
let page = getObj('page',nextBubble.pageid); | |
if ( state.lastBalloon != false ) { | |
let lastPage = getObj('page',state.lastBalloon.pageid); | |
if ( lastPage && page.id != lastPage.id ) { | |
bustBalloon(state.lastBalloon.page); | |
} | |
} | |
if (page && ! page.get("archived") ) { | |
speechBalloon(nextBubble); | |
state.SpeechBalloon.validUntil = Date.now()+(nextBubble.duration*msPerSec); | |
state.lastBalloon = nextBubble; | |
state.SpeechBalloon.bubbleShown = true; | |
} | |
} | |
} | |
}; | |
const findOrCreateBubbleParts = (thisMap,thisX,thisY) => { | |
const creationDefaults = { | |
_pageid: thisMap.id, | |
top: thisY, | |
left: thisX, | |
width: 70, | |
height: 70, | |
layer: "gmlayer" | |
}; | |
let bubbleBorder; | |
let bubbleTail; | |
let bubbleFill; | |
let bubbleText; | |
if( _.has(state.SpeechBalloon.pageGraphicMap, thisMap.id) ) { | |
bubbleBorder = getObj("graphic" , state.SpeechBalloon.pageGraphicMap[thisMap.id].BorderId); | |
bubbleTail = getObj("graphic" , state.SpeechBalloon.pageGraphicMap[thisMap.id].TailId); | |
bubbleFill = getObj("graphic" , state.SpeechBalloon.pageGraphicMap[thisMap.id].FillId); | |
bubbleText = getObj("text" , state.SpeechBalloon.pageGraphicMap[thisMap.id].TextId); | |
} | |
if ( ! bubbleBorder) { | |
bubbleBorder = createObj("graphic", _.defaults({ | |
imgsrc: "https://s3.amazonaws.com/files.d20.io/images/6565520/qJVbhBJQAw7FNDzBubKuNg/thumb.png?1417619659" | |
},creationDefaults)); | |
toFront(bubbleBorder); | |
} | |
if ( ! bubbleTail) { | |
bubbleTail = createObj("graphic", _.defaults({ | |
width: 140, | |
height: 140, | |
imgsrc: "https://s3.amazonaws.com/files.d20.io/images/6565493/BMPVhSPmlFaY_KyB7K8XHQ/thumb.png?1417619533" | |
},creationDefaults)); | |
toFront(bubbleTail); | |
} | |
if ( ! bubbleFill) { | |
bubbleFill = createObj("graphic", _.defaults({ | |
imgsrc: "https://s3.amazonaws.com/files.d20.io/images/6565524/yTHHF5NwFJcd0ddZ-9nyxg/thumb.png?1417619728" | |
},creationDefaults)); | |
toFront(bubbleFill); | |
} | |
if ( ! bubbleText) { | |
bubbleText = createObj("text", _.defaults({ | |
text: "DoubleBubbleBumBubblesDouble", | |
font_size: 16, | |
color: "rgb(0,0,0)", | |
font_family: "Courier" | |
},creationDefaults)); | |
toFront(bubbleText); | |
} | |
state.SpeechBalloon.pageGraphicMap[thisMap.id]={ | |
BorderId: bubbleBorder.id, | |
TailId: bubbleTail.id, | |
FillId: bubbleFill.id, | |
TextId: bubbleText.id | |
}; | |
return { | |
bubbleTail: bubbleTail, | |
bubbleFill: bubbleFill, | |
bubbleBorder: bubbleBorder, | |
bubbleText: bubbleText | |
}; | |
}; | |
const speechBalloon = (nextBubble) => { | |
let token = getObj('graphic',nextBubble.tokenid); | |
let theseWords = nextBubble.says; | |
let whoSaid = token.get("name"); | |
const thisMap = getObj('page',nextBubble.pageid); | |
const thisY = token.get("top"); | |
const thisX = token.get("left"); | |
if (theseWords.indexOf("--show|") != 0) { | |
sendChat(whoSaid, theseWords); | |
theseWords = wordwrap(theseWords, 28, "\n"); | |
} else { | |
theseWords = theseWords.replace("--show|", ""); | |
theseWords = theseWords.replace(/~/g, " "); | |
theseWords = theseWords.replace(/::/g, "\n"); | |
} | |
const thisParagraph = theseWords, | |
lineCount = 1 + (thisParagraph.match(/\n/g)||[]).length, | |
approximateWidth = 286, | |
approximateHeight = (lineCount * 25) + 7, | |
xAdjust = ((thisX-(thisMap.get('width') * 35)) >=0) ? -1 : 1, | |
yAdjust = ((thisY-(thisMap.get('height') * 35)) >=0) ? -1 : 1, | |
leftTail = thisX + (105 * xAdjust), | |
topTail = thisY + (105 * yAdjust), | |
leftOffsetBubble = thisX + (210 * xAdjust), | |
topOffsetBubble = thisY + ((Math.floor(approximateHeight/2) + 105 < 159 ? 159 : Math.floor(approximateHeight/2) + 105) * yAdjust), | |
bubbleParts = findOrCreateBubbleParts(thisMap,thisX,thisY), | |
tailFlipH = (-1 !== xAdjust), | |
tailFlipV = (-1 !== yAdjust); | |
if (bubbleParts.bubbleBorder) { | |
bubbleParts.bubbleBorder.set({ | |
layer: "map", | |
width: approximateWidth + 6, | |
height: approximateHeight + 6, | |
top: topOffsetBubble, | |
left: leftOffsetBubble | |
}); | |
toFront(bubbleParts.bubbleBorder); | |
} | |
if (bubbleParts.bubbleTail) { | |
bubbleParts.bubbleTail.set({ | |
layer: "map", | |
width: 140, | |
height: 140, | |
top: topTail, | |
left: leftTail, | |
fliph: tailFlipH, | |
flipv: tailFlipV | |
}); | |
toFront(bubbleParts.bubbleTail); | |
} | |
if (bubbleParts.bubbleFill) { | |
bubbleParts.bubbleFill.set({ | |
layer: "map", | |
width: approximateWidth, | |
height: approximateHeight, | |
top: topOffsetBubble, | |
left: leftOffsetBubble | |
}); | |
toFront(bubbleParts.bubbleFill); | |
} | |
if (bubbleParts.bubbleText) { | |
bubbleParts.bubbleText.set({ | |
layer: "map", | |
text: thisParagraph, | |
top: topOffsetBubble, | |
left: leftOffsetBubble | |
}); | |
toFront(bubbleParts.bubbleText); | |
} | |
state.SpeechBalloon.bubbleShown=true; | |
}; | |
const wordwrap = ( str, width, brk, cut ) => { | |
brk = brk || '\n'; | |
width = width || 75; | |
cut = cut || false; | |
if (!str) { return str; } | |
const regex = '.{1,' +width+ '}(\\s|$)' + (cut ? '|.{' +width+ '}|.+$' : '|\\S+?(\\s|$)'); | |
return str.match( RegExp(regex, 'g') ).join( brk ); | |
}; | |
const checkSelect = (obj,type) => { | |
if (obj === undefined || obj.length < 1) { | |
return false; | |
} | |
if (obj._type !== type) { | |
return false; | |
} | |
return true; | |
}; | |
const handleInput = (msg) => { | |
if ( "api" !== msg.type ) {return; } | |
const args = msg.content.split(' '); | |
const obj = _.first(msg.selected); | |
switch(args.shift()) { | |
case "!makebubble": | |
if ( ! checkSelect(obj,"graphic") ) {return; } | |
state.SpeechBalloon.queue.push({ | |
tokenid: obj._id, | |
pageid: getObj("graphic", obj._id).get('pageid'), | |
says: args.join(' '), | |
duration: defaultShowLength | |
}); | |
return; | |
case "!bustBubble": | |
if ( ! checkSelect(obj,"graphic") ) {return; } | |
bustBalloon(getObj('page',getObj("graphic", obj._id).get('pageid'))); | |
return; | |
} | |
}; | |
const registerEventHandlers = () => { | |
on('chat:message', handleInput); | |
}; | |
on("ready",()=>{ | |
checkInstall(); | |
registerEventHandlers(); | |
}); | |
return { | |
CheckInstall: checkInstall, | |
RegisterEventHandlers: registerEventHandlers | |
}; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment