Last active
September 12, 2020 05:34
-
-
Save vicberg/6635c77f7f730529ccf83f64819297c0 to your computer and use it in GitHub Desktop.
Roll20 Audio Master v.2.0
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
/* | |
Roll20AM script: | |
Author: Berg V. | |
Contact: https://app.roll20.net/users/3135709/victor b | |
Thanks to: The Aaron, Arcane Scriptomancer and Stephen S. for their help Alpha and Beta Testing this script. Recent modifications by Victor B. | |
*/ | |
var Roll20AM = Roll20AM || (function() { | |
'use strict'; | |
var version = '2.13', | |
lastUpdate = 1491352004, | |
schemaVersion = 2.02, | |
debug = false, | |
displayTrack = false, | |
helpLink, | |
delays = [], | |
defaults = { | |
css: { | |
button: { | |
//'border-radius': '1em', | |
'background-color': '#006dcc', | |
'margin': '0px', | |
'font-weight': 'bold', | |
'padding': '-5px', | |
'color': 'white', | |
'border-style':'none' | |
} | |
} | |
}, | |
templates = {}, | |
playImage = '4', | |
stopImage = '6', | |
tagImage = '3', | |
noTagImage = 'd', | |
deleteImage = 'D', | |
shuffleImage = ';', | |
randomSingleImage = '`', | |
randomLoopImage = '?', | |
togetherImage = 'J', | |
loopImage = 'r', | |
singleImage = '1', | |
lockImage = ')', | |
unlockImage = '(', | |
backImage = 'y', | |
fadeInImage = '7', | |
fadeOutImage = '8', | |
decreaseImage = '<', | |
increaseImage = '>', | |
ch = function (c) { | |
var entities = { | |
'<' : 'lt', | |
'>' : 'gt', | |
"'" : '#39', | |
'@' : '#64', | |
'{' : '#123', | |
'|' : '#124', | |
'}' : '#125', | |
'[' : '#91', | |
']' : '#93', | |
'"' : 'quot', | |
'-' : 'mdash', | |
' ' : 'nbsp' | |
}; | |
if(_.has(entities,c) ){ | |
return ('&'+entities[c]+';'); | |
} | |
return ''; | |
}, | |
HE = (function(){ | |
const esRE = (s) => s.replace(/(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$|\-|\:)/g,'\\$1'); | |
const e = (s) => `&${s};`; | |
const entities = { | |
'<' : e('lt'), | |
'>' : e('gt'), | |
"'" : e('#39'), | |
'@' : e('#64'), | |
'{' : e('#123'), | |
'|' : e('#124'), | |
'}' : e('#125'), | |
'[' : e('#91'), | |
']' : e('#93'), | |
'"' : e('quot'), | |
'-' : e('#45'), | |
':' : e('#58'), | |
'.' : e('#46') | |
}; | |
const re = new RegExp(`(${Object.keys(entities).map(esRE).join('|')})`,'g'); | |
return (s) => s.replace(re, (c) => (entities[c] || c) ); | |
})(), | |
checkInstall = function() { | |
log('-=> Roll20AM v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); | |
if( ! _.has(state,'Roll20AM') || state.Roll20AM.version !== schemaVersion) { | |
state.Roll20AM=state.Roll20AM || {}; | |
log(' > Updating Schema to v'+schemaVersion+' <'); | |
state.Roll20AM.version = schemaVersion; | |
} | |
initialize(); | |
if(!state.Roll20AM.help || !getObj('handout',state.Roll20AM.help)){ | |
if(findObjs({type:'handout',name:'Roll20AM'})[0]){ | |
state.Roll20AM.help = findObjs({type:'handout',name:'Roll20AM'})[0].id; | |
}else{ | |
state.Roll20AM.help = createObj('handout',{ | |
name:'Roll20AM', | |
inplayerjournals:'all', | |
archived:true | |
}).id; | |
} | |
} | |
log(' >Roll20AM has had a significant update. The command syntax has changed and any currently existing macros will need to be updated. Please check the **[forum thread](permalink to your release post)**'); | |
log(' >Stored settings loaded<'); | |
// storeTracks(); | |
// log(' >Tracks updated<'); | |
generateHelp(); | |
log(' >Help handout updated<'); | |
buildTemplates(); | |
helpLink = 'https://journal.roll20.net/handout/'+state.Roll20AM.help; | |
}, | |
buildParty = function(){ | |
//Create a list of Players and GM | |
var partyList = '|All,All'; | |
_.each(findObjs({type:'player'}),player=>{ | |
let who = getObj('player', player.id).get('displayname'); | |
if (playerIsGM(player.id)){ | |
who = 'gm' | |
} | |
partyList += '|'+who+','+who | |
}); | |
return partyList; | |
}, | |
initialize = function(){ | |
state.Roll20AM={ | |
trackDetails:state.Roll20AM.trackDetails || {},/*object index by track id containing These properties: | |
volume: the tracks individual volume; is multiplied by the masterVolume to set the jukebox volume | |
delays: number of unresolved delays involving this track*/ | |
playLists:state.Roll20AM.playLists || {},/*Object of objects with the following properties indexed by playlist name | |
name: the name of the playlist | |
trackids: array of the trackids of tracks in this playlist | |
playing:true/false | |
mode: one of 'shuffle', 'trueRandom', 'together', single, or through (default is single). Null indicates the playlist should play in the default behavior of straight through | |
currentTrack:Array of the tracks this playlist is currently playing | |
shuffleids:Array of tracks ordered randomly | |
lastTrack:String containing the id of the last track to be played | |
delays:The number of times this playlist has been delayed. Incremented in handleInput, decremented in delayHandler. Actions are not completed if there are no delays to support them. | |
access:String defining whether the playlist is player accessible or not. 'gm' or 'player'*/ | |
masterVolume:state.Roll20AM.masterVolume || 50,//The mastervolume, all volumes are multiplied by this before being applied to the actual track. Maximum/Minimum volume levels applicable to tracks are 0-100, the multiplied final volume is set to the boundary if it is outside the range. | |
fadeVolumeUp:state.Roll20AM.fadeVolumeUp || 50,//The fadevolume while increasing volume. Default desired volume level. | |
fadeVolumeDown:state.Roll20AM.fadeVolumeDown || 0,//The fadevolume while decreasing volume. Default desired volume level. | |
fadeTime:state.Roll20AM.fadeTime || 10,//The default time to reach desired level in seconds | |
delay:state.Roll20AM.delay || 0,//The default delay time in seconds. | |
restrict:state.Roll20AM.restrict || 'on',//Whether player restrictions are on or not, string that will be either 'on' or 'off' | |
API:state.Roll20AM.API || 'gm',//String denoting how API messages are treated. Either 'gm' or 'player' | |
access:state.Roll20AM.access || 'None',//Limited or Full access to Roll20AM for Players | |
tag:state.Roll20AM.tag || '-players-',//the player access tag that should be appended to tracks that are player accessible | |
menu:state.Roll20AM.menu, | |
nomenu:state.Roll20AM.nomenu || 'On' | |
}; | |
}, | |
buildTemplates = function() { | |
templates.cssProperty =_.template( | |
'<%=name %>: <%=value %>;' | |
); | |
templates.style = _.template( | |
'style="<%='+ | |
'_.map(css,function(v,k) {'+ | |
'return templates.cssProperty({'+ | |
'defaults: defaults,'+ | |
'templates: templates,'+ | |
'name:k,'+ | |
'value:v'+ | |
'});'+ | |
'}).join("")'+ | |
' %>"' | |
); | |
templates.button = _.template( | |
'<a <%= templates.style({'+ | |
'defaults: defaults,'+ | |
'templates: templates,'+ | |
'css: _.defaults(css,defaults.css.button)'+ | |
'}) %> href="<%= command %>"><%= label %></a>' | |
); | |
}, | |
makeImageButton = function(command, image, toolTip, backgroundColor,size){ | |
return '<a href="'+command+'" title= "'+toolTip+'" style="margin:0;padding:1px;background-color:'+backgroundColor+'"><span style="color:black;padding:0px;font-size:'+size+'px;font-family: \'Pictos\'">'+image+'</span></a>' | |
}, | |
makeTextButton = function(command, label, backgroundColor, color){ | |
return '<a href="'+command+'" style="margin:0;border: 0;background-color:'+backgroundColor+'"><span style="font-style:bold;color:'+color+'">'+label+'</span></a>' | |
}, | |
makeButton = function(command, label, backgroundColor, color){ | |
return templates.button({ | |
command: command, | |
label: label, | |
templates: templates, | |
defaults: defaults, | |
css: { | |
color: color, | |
'background-color': backgroundColor | |
} | |
}); | |
}, | |
outputConfig = function(menu,who,filter){ | |
var mvTextButton,tracksButton,playlistButton,restrictButton,volumeButton,muteButton,playButton,addButton,increaseButton,decreaseButton,fadeInButton,tagButton, | |
deleteButton,APIButton,tagButton,templateButton,commandButton,rollButton,textButton,accessButton,modeButton,importButton,backButton,fadeOutButton,tagDeleteButton, | |
output,outputWho; | |
if (state.Roll20AM.API == 'gm'){ | |
outputWho = '/w gm'; | |
}else if (state.Roll20AM.API == "All"){ | |
outputWho = ' '; | |
}else{ | |
outputWho = '/w '+state.Roll20AM.API; | |
} | |
output = ' <div style="border: 1px solid black; background-color: white; padding: 3px 3px;margin-top:20px">' | |
+'<div style="font-weight: bold; border-bottom: 1px solid black;font-size: 100%;style="float:left;">'; | |
if (debug){ | |
log('Output Config:'+menu) | |
} | |
if (menu){ | |
if (menu == 'tracks'){ | |
output += ' Roll20 AM('+version+')<b> Tracks</b></div>'; | |
_.each(state.Roll20AM.trackDetails,(track)=>{ | |
output += outputTrack(track,menu) | |
}); | |
backButton = makeImageButton('!roll20AM --config',backImage,'Return to Config','transparent'); | |
output += '<div style="float:right;display:inline-block;margin-top:5px">'+backButton+'</div>'; | |
output += '</div>'; | |
}else if (menu == 'tags'){ | |
output += ' Roll20 AM('+version+')<b> Tags</b></div>'; | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
if (list.name == 'Tag1' || list.name == 'Tag2' || list.name == 'Tag3' || list.name == "Tag4"){ | |
output += outputList(list,list.name,'tags') | |
} | |
}); | |
backButton = makeImageButton('!roll20AM --config',backImage,'Return to Config','transparent'); | |
output += '<div style="float:right;display:inline-block;margin-top:5px">'+backButton+'</div>'; | |
output += '</div>'; | |
}else if (menu == 'playlists'){ | |
output += ' Roll20 AM('+version+')<b> Playlists</b></div>'; | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
if (list.name != 'Tag1' && list.name != 'Tag2' && list.name != 'Tag3' && list.name != "Tag4"){ | |
output += outputList(list,list.name,'playlists') | |
} | |
}); | |
backButton = makeImageButton('!roll20AM --config',backImage,'Return to Setup','transparent'); | |
output += '<div style="float:right;display:inline-block;margin-top:5px">'+backButton+'</div>'; | |
output += '</div>'; | |
}else if(state.Roll20AM.playLists[menu]){ | |
output += ' Roll20 AM('+version+')<b>'+menu+' Playlist</b></div>'; | |
_.each(state.Roll20AM.playLists[menu].trackids,(t)=>{ | |
var track = getTrackDetails(t); | |
output += outputTrack(track,menu,menu) | |
}); | |
if (menu == 'Tag1' || menu == 'Tag2' || menu == 'Tag3' || menu == 'Tag4'){ | |
backButton = makeImageButton('!roll20AM --config,menu=tags',backImage,'Return to Tags','transparent'); | |
}else{ | |
backButton = makeImageButton('!roll20AM --config,menu=playlists',backImage,'Return to Playlists','transparent'); | |
} | |
output += '<div style="float:right;display:inline-block;margin-top:5px">'+backButton+'</div>'; | |
output += '</div>'; | |
} | |
}else{ | |
output += ' Roll20 AM (' +version+ ') - <b> Setup</b></div><div style="padding-left:10px;margin-bottom:3px;">'; | |
//Master Volume Control | |
mvTextButton = makeButton('!roll20AM --edit,volume,level=?{Volume Level (0-100)|'+state.Roll20AM.masterVolume+'} --config',state.Roll20AM.masterVolume+'%','white','black'); | |
output += '<b>Master Volume: </b><div style="display:inline-block">'+mvTextButton+'</div><br>'; | |
//Fade Time | |
mvTextButton = makeButton('!roll20AM --edit,fade,time,level=?{Fade Time (Seconds) (0-200)|'+state.Roll20AM.fadeTime+'} --config',state.Roll20AM.fadeTime+' (Seconds)','white','black'); | |
output += '<b>Fade Time: </b><div style="display:inline-block">'+mvTextButton+'</div><br>'; | |
//Delay Time | |
mvTextButton = makeButton('!roll20AM --edit,delayed,level=?{Delayed (Seconds) (0-200)|'+state.Roll20AM.delay+'} --config',state.Roll20AM.delay+' (Seconds)','white','black'); | |
output += '<b>Delayed: </b><div style="display:inline-block">'+mvTextButton+'</div><br>'; | |
//Playlists | |
mvTextButton = makeButton('!roll20AM --config,menu=playlists','Playlists','white','black'); | |
output += '<div>'+mvTextButton+'</div>'; | |
//Tracks | |
mvTextButton = makeButton('!roll20AM --config,menu=tracks','Tracks','white','black'); | |
output += '<div>'+mvTextButton +'</div>'; | |
//Tags | |
mvTextButton = makeButton('!roll20AM --config,menu=tags','Tags','white','black'); | |
output += '<div>'+mvTextButton +'</div>'; | |
//Tracks | |
mvTextButton = makeButton('!roll20AM --audio,stop|','Stop All Tracks','white','black'); | |
output += '<div>'+mvTextButton +'</div>'; | |
//Import Jukebox | |
mvTextButton = makeButton('!roll20AM --config,import','Import Jukebox','white','black'); | |
output += '<div>'+mvTextButton+'</div>'; | |
//Import Jukebox | |
mvTextButton = makeButton('!roll20AM --config,remove','Remove All','white','black'); | |
output += '<div>'+mvTextButton+'</div>'; | |
//Viewed By Button | |
mvTextButton = makeButton('!roll20AM --config,viewBy=?{Who Can View'+buildParty()+'} --config',state.Roll20AM.API,'white','black'); | |
output += '<b>Viewed By: </b><div style="display:inline-block">'+mvTextButton+'</div><br>'; | |
//Access Button | |
mvTextButton = makeButton('!roll20AM --config,access=?{Access|None,None|Limited,Limited|Full,Full} --config',state.Roll20AM.access,'white','black'); | |
output += '<b>Access: </b><div style="display:inline-block">'+mvTextButton+'</div><br>'; | |
//Access Button | |
mvTextButton = makeButton('!roll20AM --config,display=?{Display|On,On|Off,Off} --config',state.Roll20AM.nomenu,'white','black'); | |
output += '<b>Menu Display: </b><div style="display:inline-block">'+mvTextButton+'</div><br>'; | |
//ends the padding div begun before listenButton | |
output += '</div>'; | |
} | |
//ends the first div | |
output += '<div style="margin-bottom:30px;">' | |
output += '</div>'; | |
if (menu){ | |
if (state.Roll20AM.menu != 'nomenu' && state.Roll20AM.nomenu == 'On'){ | |
sendChat(who,outputWho + output,null,{noarchive:true}); | |
//if Roll20AM is configured to send menus to a player, then send again to GM | |
if (state.Roll20AM.API != 'All' && state.Roll20AM.API != 'gm'){ | |
sendChat(who,'/w gm' + output,null,{noarchive:true}); | |
} | |
} | |
} else { | |
sendChat(who,outputWho + output,null,{noarchive:true}); | |
//if Roll20AM is configured to send menus to a player, then send again to GM | |
if (state.Roll20AM.API != 'All' && state.Roll20AM.API != 'GM'){ | |
sendChat(who,'/w gm' + output,null,{noarchive:true}); | |
} | |
} | |
}, | |
//output each individual track. Called from menus above | |
outputTrack = function(track,menu){ | |
var title = track.title.split('by'),output,accessButton,playButton,modeButton,increaseButton,decreaseButton,fadeButton,fadeOutButton,fadeInButton,playlistButton,tagButton,deleteButton,mvTextButton | |
if (track.playing){ | |
output = '<div style="background-color:#33cc33;width:100%;padding:2px;font-style:bold;color:white">'+title[0]+'</div>' | |
}else{ | |
output = '<div style="background-color:#ff5050;width:100%;padding:2px;font-style:bold;color:white">'+title[0]+'</div>' | |
} | |
//Access Toggle | |
if(track.access === 'gm'){ | |
accessButton = makeImageButton('!roll20AM --edit,access,player|'+encodeURIComponent(track.title)+' --config,menu='+menu,unlockImage,'Set List Access to player','transparent',15); | |
}else{ | |
accessButton = makeImageButton('!roll20AM --edit,access,gm|'+encodeURIComponent(track.title)+' --config,menu='+menu,lockImage,'Set List Access to gm only','transparent',15); | |
} | |
//Play Toggle | |
if(track.playing){ | |
playButton = makeImageButton('!roll20AM --audio,stop|'+encodeURIComponent(track.title)+' --config,menu='+menu,stopImage,'Stop Track','transparent',15); | |
}else{ | |
playButton = makeImageButton('!roll20AM --audio,play|'+encodeURIComponent(track.title)+' --config,menu='+menu,playImage,'Play Track','transparent',15); | |
} | |
//Shuffle Toggle | |
if (track.mode == "single" ){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,loop|'+encodeURIComponent(track.title)+' --config,menu='+menu,singleImage,'Set the playlist playmode; currently single','transparent',15); | |
}else if (track.mode == "loop" ){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,single|'+encodeURIComponent(track.title)+' --config,menu='+menu,loopImage,'Set the playlist playmode; currently loop','transparent',15); | |
} | |
//Track Increase Button | |
increaseButton = makeImageButton('!roll20AM --audio,increase|'+encodeURIComponent(track.title)+' --config,menu='+menu,increaseImage,'Increase Volume','transparent',15); | |
//Track Decrease Button | |
decreaseButton = makeImageButton('!roll20AM --audio,decrease|'+encodeURIComponent(track.title)+' --config,menu='+menu,decreaseImage,'Decrease Volume','transparent',15); | |
//Track Decrease Button | |
deleteButton = makeImageButton('!roll20AM --edit,remove|'+encodeURIComponent(track.title)+' --config,menu='+menu,deleteImage,'Delete Track','transparent',15); | |
//Fade Out Button | |
if (track.playing){ | |
fadeButton = makeImageButton('!roll20AM --audio,fade,out|'+encodeURIComponent(track.title),fadeOutImage,'Fade Out','transparent',15); | |
}else{ | |
fadeButton = makeImageButton('!roll20AM --audio,fade,in|'+encodeURIComponent(track.title)+' --config,menu='+menu,fadeInImage,'Fade In','transparent,15'); | |
} | |
//Track Volume Control | |
mvTextButton = makeButton('!roll20AM --edit,volume,level=?{Volume Level (0-100)|'+track.volume+'}|'+encodeURIComponent(track.title)+' --config,menu='+menu,track.volume+'%','white','black'); | |
playlistButton = '<div style="display:inline-block;font-size:10px;margin-right:3px"><b>Vol:</b>'+mvTextButton+'</div>'; | |
//Track Fade Time | |
mvTextButton = makeButton('!roll20AM --edit,fade,time,level=?{Fade Time in Seconds (0-200)|'+track.fadetime+'}|'+encodeURIComponent(track.title)+' --config,menu='+menu,track.fadetime,'white','black'); | |
playlistButton += '<div style="display:inline-block;font-size:10px;margin-right:3px"><b>Fade Time:</b>'+mvTextButton+'</div>'; | |
//Track Delay Time | |
mvTextButton = makeButton('!roll20AM --edit,delayed,level=?{Delay Time in Seconds (0-200)|'+track.delay+'}|'+encodeURIComponent(track.title)+' --config,menu='+menu,track.delay,'white','black'); | |
playlistButton += '<div style="display:inline-block;font-size:10px"><b>Delayed:</b>'+mvTextButton+'</div>'; | |
//Tags | |
if (track.tags[0] == 'On'){ | |
tagButton = '<div style="font-size:80%;line-height:1em;display:inline-block;">1</div>'+makeImageButton('!roll20AM --edit,unset,tag1|'+encodeURIComponent(track.title)+' --config,menu='+menu,tagImage,'Assigned to Tag1','transparent',10); | |
}else{ | |
tagButton = '<div style="font-size:80%;line-height:1em;display:inline-block;">1</div>'+makeImageButton('!roll20AM --edit,set,tag1|'+encodeURIComponent(track.title)+' --config,menu='+menu,noTagImage,'Not Assigned to Tag1','transparent',10); | |
} | |
if (track.tags[1] == 'On'){ | |
tagButton += '<div style="font-size:80%;line-height:1em;display:inline-block;">2</div>'+makeImageButton('!roll20AM --edit,unset,tag2|'+encodeURIComponent(track.title)+' --config,menu='+menu,tagImage,'Assigned to Tag2','transparent',10); | |
}else{ | |
tagButton += '<div style="font-size:80%;line-height:1em;display:inline-block;">2</div>'+makeImageButton('!roll20AM --edit,set,tag2|'+encodeURIComponent(track.title)+' --config,menu='+menu,noTagImage,'Not Assigned to Tag2','transparent',10); | |
} | |
if (track.tags[2] == 'On'){ | |
tagButton += '<div style="font-size:80%;line-height:1em;display:inline-block;">3</div>'+makeImageButton('!roll20AM --edit,unset,tag3|'+encodeURIComponent(track.title)+' --config,menu='+menu,tagImage,'Assigned to Tag2','transparent',10); | |
}else{ | |
tagButton += '<div style="font-size:80%;line-height:1em;display:inline-block;">3</div>'+makeImageButton('!roll20AM --edit,set,tag3|'+encodeURIComponent(track.title)+' --config,menu='+menu,noTagImage,'Not Assigned to Tag2','transparent',10); | |
} | |
if (track.tags[3] == 'On'){ | |
tagButton += '<div style="font-size:80%;line-height:1em;display:inline-block;">4</div>'+makeImageButton('!roll20AM --edit,unset,tag4|'+encodeURIComponent(track.title)+' --config,menu='+menu,tagImage,'Assigned to Tag2','transparent',10); | |
}else{ | |
tagButton += '<div style="font-size:80%;line-height:1em;display:inline-block;">4</div>'+makeImageButton('!roll20AM --edit,set,tag4|'+encodeURIComponent(track.title)+' --config,menu='+menu,noTagImage,'Not Assigned to Tag2','transparent',10); | |
} | |
//Put it all together | |
output+=playlistButton+'<div>'+playButton+' '+fadeButton+' '+decreaseButton+' '+increaseButton+' '+modeButton+' '+accessButton+' '+deleteButton +'<div style="display:inline-block;float:right;font-size:80%;">'+tagButton+'</div></div>' | |
output+='<div style="margin-top:5px"></div>' | |
return output; | |
}, | |
outputList = function(list,menu,mainMenu){ | |
var output,accessButton,playButton,modeButton,increaseButton,decreaseButton,fadeButton,fadeOutButton,fadeInButton,playlistButton,tagButton,deleteButton,mvTextButton | |
if(list.playing){ | |
playlistButton = '<div style="background-color:#33cc33;width:99%">'+makeTextButton('!roll20AM --config,menu='+menu,list.name,'transparent','white')+'</div>'; | |
}else{ | |
playlistButton = '<div style="background-color:#ff5050;width:99%">'+makeTextButton('!roll20AM --config,menu='+menu,list.name,'transparent','white')+'</div>'; | |
} | |
//Access Toggle | |
if(list.access === 'gm'){ | |
accessButton = makeImageButton('!roll20AM --edit,access,player|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,unlockImage,'Set List Access to player','white',15); | |
}else{ | |
accessButton = makeImageButton('!roll20AM --edit,access,gm|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,lockImage,'Set List Access to gm only','white',15); | |
} | |
//Play Toggle | |
if(list.playing){ | |
playButton = makeImageButton('!roll20AM --audio,stop|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,stopImage,'Stop Playlist','white',15); | |
}else{ | |
playButton = makeImageButton('!roll20AM --audio,play|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,playImage,'Play Playlist','white',15); | |
} | |
//Shuffle Toggle | |
if (list.mode == "randomSingle"){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,random,loop|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,randomSingleImage,'Set the playlist playmode; currently random single','white',15); | |
}else if (list.mode == "randomLoop"){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,shuffle|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,randomLoopImage,'Set the playlist playmode; currently random loop','white',15); | |
}else if (list.mode == "shuffle" ){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,single|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,shuffleImage,'Set the playlist playmode; currently shuffled','white',15); | |
}else if (list.mode == "single" ){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,loop|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,singleImage,'Set the playlist playmode; currently single','white',15); | |
}else if (list.mode == "loop" ){ | |
modeButton = makeImageButton('!roll20AM --edit,mode,together|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,loopImage,'Set the playlist playmode; currently loop','white',15); | |
}else{ | |
modeButton = makeImageButton('!roll20AM --edit,mode,random,single|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,togetherImage,'Set the playlist playmode; currently together','white'); | |
} | |
//Increase Button | |
increaseButton = makeImageButton('!roll20AM --audio,increase|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,increaseImage,'Increase Volume','white',15); | |
//Decrease Button | |
decreaseButton = makeImageButton('!roll20AM --audio,decrease|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,decreaseImage,'Decrease Volume','white',15); | |
//Decrease Button | |
deleteButton = makeImageButton('!roll20AM --edit,remove|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,deleteImage,'Delete Playlist','white'); | |
//Fade Button | |
if (list.playing){ | |
fadeButton = makeImageButton('!roll20AM --audio,fade,out|'+encodeURIComponent(list.name),fadeOutImage,'Fade Out','white',15); | |
}else{ | |
fadeButton = makeImageButton('!roll20AM --audio,fade,in|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,fadeInImage,'Fade In','white',15); | |
} | |
//Playlist Volume Control | |
mvTextButton = makeButton('!roll20AM --edit,volume,level=?{Volume Level (0-100)|'+list.volume+'}|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,list.volume+'%','white','black'); | |
playlistButton += '<div style="display:inline-block;font-size:10px;margin-right:3px"><b>Vol:</b>'+mvTextButton+'</div>'; | |
//Playlist Fade Time | |
mvTextButton = makeButton('!roll20AM --edit,fade,time,level=?{Fade Time in Seconds (0-200)|'+list.fadetime+'}|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,list.fadetime,'white','black'); | |
playlistButton += '<div style="display:inline-block;font-size:10px;margin-right:3px"><b>Fade Time:</b>'+mvTextButton+'</div>'; | |
//Playlist Fade Time | |
mvTextButton = makeButton('!roll20AM --edit,delayed,level=?{Delay Time in Seconds (0-200)|'+list.delay+'}|'+encodeURIComponent(list.name)+' --config,menu='+mainMenu,list.delay,'white','black'); | |
playlistButton += '<div style="display:inline-block;font-size:10px;"><b>Delayed:</b>'+mvTextButton+'</div>'; | |
//Output of all of the above | |
output = playlistButton+'<div style="float:center;">' +playButton+' '+fadeButton+' '+decreaseButton+' '+increaseButton+' ' +modeButton+' '+accessButton+ ' ' +deleteButton + '</div>'; | |
output +='<div style="font-weight: bold; border-bottom: 1px solid black;font-size: 90%;style="float:left;"></div>' | |
return output; | |
}, | |
//****************************************************************************************************************************** | |
//Handler Functions | |
// - RegisterEventHandlers drives this entire process. These are events detected by roll20 outside of this script. | |
// - chat message invokes inputHandler | |
// - Jukebox track finishes invokes changeHandler | |
// - Adding a new track to Jukebox updates state.Roll20AM | |
// - Deleting a jukebox track updates state.Roll20AM | |
// - inputHandler receives the Roll20AM command,parses it and processes it | |
// - commandHandler processed input commands using setTimeout and Delay Time (if any) | |
// - listHandler processes list related functions | |
// - trackHandler processes track related functions | |
// - editHandler processes list and track related edit functions | |
// - configHandler processes configuration related functions, some send output to chat window | |
//****************************************************************************************************************************** | |
inputHandler = function(msg_orig) { | |
//must be roll20AM to start all this | |
if (msg_orig.content.indexOf('!roll20AM')!==0){ | |
return; | |
} | |
var msg = _.clone(msg_orig),cmdDetails,args,restrict,player,who | |
if(state.Roll20AM.restrict==='off'||playerIsGM(msg.playerid) ||(msg.playerid==='API' && state.Roll20AM.API==='gm')){//If the gm has turned off player restrictions, or this is a gm, or it's an API generated message and the API is treated like a gm | |
restrict = false;//set this run through of the script to not restrict access to tracks | |
}else{ | |
restrict = true;//set this run through of the script to restrict access to tracks | |
} | |
if(msg.playerid==='API'){ | |
who = 'gm';//since we're going to be using who to determine who to send later chat messages to, it needs to be a valid option for /w. So, a generic "gm" will work for probably 95% of users | |
}else{ | |
who = getObj('player', msg.playerid).get('displayname'); | |
} | |
if(_.has(msg,'inlinerolls')){//calculates inline rolls | |
msg.content = inlineExtract(msg); | |
} | |
//splits the message contents into discrete arguments | |
args = msg.content.split(/\s+--/); | |
if(args[0] === '!roll20AM'){ | |
if(args[1]){ | |
_.each(_.rest(args,1),(cmd) =>{ | |
//Extract Commands | |
cmdDetails = cmdExtract(cmd); | |
if (debug){ | |
log(cmdDetails) | |
} | |
var tracklists=[],playlists=[],timer,list | |
//cmdDetails.tracks holds tracks/playlists, everything after the |. If playlist push into array | |
_.each(cmdDetails.tracks,(listName)=>{ | |
if(state.Roll20AM.playLists[listName]){ | |
playlists.push(listName); | |
}; | |
}); | |
//if track, pushing into array | |
_.each(cmdDetails.tracks,(trackTitle)=>{ | |
if(state.Roll20AM.trackDetails[trackTitle]){ | |
tracklists.push(trackTitle); | |
}; | |
}); | |
if (debug){ | |
log('Input Handler Action:' + cmdDetails.action) | |
log('Lists:' + playlists) | |
log('Tracks:' + tracklists) | |
} | |
if (cmdDetails.details.nomenu){ | |
state.Roll20AM.menu = cmdDetails.details.nomenu | |
} | |
//prevent players from doing things they shouldn't. Playlist level actions and edit commands | |
if (restrict){ | |
if (cmdDetails.action == 'edit' ){ | |
sendChat(who,'Invalid Roll20AM Command. Restricted action not allowed by a player.',null,{noarchive:true}); | |
return; | |
} | |
} | |
commandHandler(cmdDetails,playlists,tracklists,restrict,who) | |
}) | |
} | |
} | |
}, | |
//Processes the commands based on Delay Time (if any) | |
commandHandler = function(cmdDetails,playlists,tracklists,restrict,who){ | |
if (debug){ | |
log ('Command Handler') | |
} | |
setTimeout(function(){ | |
if (cmdDetails.action == 'audio'){ | |
if (playlists.length > 0){ | |
_.each(playlists,(listName)=>{ | |
listHandler(cmdDetails,getPlayList(listName),who,restrict); | |
}); | |
} | |
if (tracklists.length > 0){ | |
_.each(tracklists,(trackTitle)=>{ | |
trackHandler(cmdDetails,getTrackDetails(getTrackID(trackTitle)),who,restrict); | |
}); | |
} | |
if (cmdDetails.details.stop){ | |
if (playlists.length == 0 && tracklists.length == 0){ | |
stopAll(who) | |
} | |
} | |
//edit commands | |
}else if (cmdDetails.action == 'edit'){ | |
if (playlists.length > 0){ | |
_.each(playlists,(listName)=>{ | |
editHandler(cmdDetails,getPlayList(listName),null,who,restrict) | |
}); | |
} | |
if (tracklists.length > 0){ | |
_.each(tracklists,(trackTitle)=>{ | |
editHandler(cmdDetails,null,getTrackDetails(getTrackID(trackTitle)),who,restrict) | |
}); | |
} | |
if (tracklists.length == 0 && playlists.length == 0 ){ | |
editHandler(cmdDetails,null,null,who,restrict) | |
} | |
//config commands | |
}else if (cmdDetails.action == 'config'){ | |
configHandler(cmdDetails,who,restrict) | |
}else{ | |
sendChat(who,'Invalid Roll20AM Command. Valid commands are --audio, --edit, --config',null,{noarchive:true}); | |
} | |
},cmdDetails.details.delay) | |
}, | |
//Extracts inline rolls | |
inlineExtract = function(msg){ | |
return _.chain(msg.inlinerolls) | |
.reduce(function(m,v,k){ | |
m['$[['+k+']]']=v.results.total || 0; | |
return m; | |
},{}) | |
.reduce(function(m,v,k){ | |
return m.replace(k,v); | |
},msg.content) | |
.value(); | |
}, | |
//Extracts the command details from a command string passed from handleInput | |
cmdExtract = function(cmd){ | |
var cmdSep = { | |
details:{} | |
}, | |
vars, | |
raw, | |
tracks, | |
command, | |
details; | |
if (debug){ | |
log('Command Extract') | |
log('Command String:' + cmd) | |
} | |
//split the commands from the tracks or playlists | |
raw = cmd.split('|') | |
command = raw[0]; | |
tracks = decodeURIComponent(raw[1]); | |
//split multiple tracks into array | |
if (tracks){ | |
tracks = tracks.split(','); | |
} | |
if (debug){ | |
log('Command:' + command) | |
log('Command Tracks:' + tracks) | |
} | |
cmdSep.tracks=_.map(tracks,(t)=>{ | |
return t.trim(); | |
}); | |
//find the action and set the cmdSep Action | |
cmdSep.action = command.match(/audio|config|edit/); | |
//the ./ is an escape within the URL so the hyperlink works. Remove it | |
command.replace('./', ''); | |
//split additional command actions | |
_.each(command.replace(cmdSep.action+',','').split(','),(d)=>{ | |
vars=d.match(/(volTick|time|volume|delay|level|menu|mode|restrict|tag|API|access|tag|tag1|tag2|tag3|tag4|tag5|set|unset|filter|delayed|viewBy|display|)(?:\:|=)([^,]+)/) || null; | |
if(vars){ | |
cmdSep.details[vars[1]]=vars[2]; | |
}else{ | |
cmdSep.details[d]=d; | |
} | |
}); | |
cmdSep.details.delay = cmdSep.details.delay*1000 || 0; | |
return cmdSep; | |
}, | |
//changeHandler invoked when a track finishes in Jukebox. | |
changeHandler = function(obj,prev){ | |
var id, listFound = false, trackDetails, trackFound = false | |
//we receive the track object from jukebox. softstop is set to true upon finish | |
if(obj.get('softstop')===true && prev.softstop === false){ | |
var trackDetails=getTrackDetails(obj.get('_id')),restrict | |
if (debug){ | |
log('Change Handler') | |
log('Track ID:'+trackDetails.id) | |
log('Track Finished:'+trackDetails.title) | |
} | |
//find out of a playlist is currently playing | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
if(list.playing){ | |
//check if this track in this playlist, if so, continue with playlist based on mode | |
if (list.currentTrack.indexOf(trackDetails.id) > -1 ){ | |
if (trackDetails.playing) | |
{ | |
listFound = true | |
trackDetails.playing = false | |
if (debug){ | |
log('Track Found in List:' + list.name) | |
log('List Mode:' + list.mode) | |
} | |
if (list.mode == 'loop'){ | |
playListInOrder(list,null,trackDetails.who) | |
} | |
if (list.mode == 'randomSingle'){ | |
list.playing = false | |
outputConfig('playlists',trackDetails.who); | |
} | |
if (list.mode == 'randomLoop'){ | |
playListRandom(list,null,trackDetails.who) | |
} | |
if (list.mode == 'shuffle'){ | |
playListShuffle(list,null,trackDetails.who) | |
} | |
if (list.mode == 'single'){ | |
list.playing = false | |
outputConfig('playlists',trackDetails.who); | |
} | |
} | |
} | |
} | |
}) | |
// if track wasn't found in active playlist, set ROLL20AM state to not playing and output menu so icon changes from play to stop | |
if (!listFound){ | |
_.each(state.Roll20AM.trackDetails,track=>{ | |
if (track.playing){ | |
trackFound = true; | |
} | |
}) | |
if (!listFound && !trackFound){ | |
return; | |
} | |
trackDetails.playing = false; | |
//if a track is launched within a playlist, set playlist to off | |
_.each(state.Roll20AM.playLists,list=>{ | |
if (list.trackids.indexOf(trackDetails.id) > -1){ | |
list.playing = false | |
} | |
}) | |
if (trackDetails.mode == 'loop'){ | |
playTrack(trackDetails.id,trackDetails.volume,null,trackDetails.who) | |
}else{ | |
outputConfig(state.Roll20AM.menu,trackDetails.who); | |
} | |
} | |
} | |
}, | |
listHandler = function(cmdDetails,list,who,restrict){ | |
if (debug){ | |
log("List Handler Action:" + cmdDetails.action) | |
log("List Handler List:" + list.name) | |
log("List Handler Mode:" + list.mode) | |
} | |
if (restrict && list.access == 'gm'){ | |
sendChat(who,'Invalid Roll20AM Command. Restricted action not allowed by a player.',null,{noarchive:true}); | |
return; | |
} | |
if (list.trackids.length == 0){ | |
sendChat(who,'Cannot launch Playlists. No Tracks assigned.',null,{noarchive:true}); | |
return; | |
} | |
//play depending on mode | |
if (cmdDetails.details.play){ | |
if (list.mode == 'loop'){ | |
playListInOrder(list,null,who); | |
} | |
if (list.mode == 'randomSingle' || list.mode == 'randomLoop'){ | |
playListRandom(list,null,who); | |
} | |
if (list.mode == 'shuffle'){ | |
playListShuffle(list,null,who); | |
} | |
if (list.mode == 'single'){ | |
playListInOrder(list,null,who); | |
} | |
if (list.mode == 'together'){ | |
playListTogether(list,null,who); | |
} | |
}else if (cmdDetails.details.stop){ | |
stopList(list,who); | |
}else if (cmdDetails.details.fade){ | |
if (cmdDetails.details.in){ | |
fadeListIn(list,who); | |
} | |
if (cmdDetails.details.out){ | |
fadeListOut(list,cmdDetails.details.nomenu,who); | |
} | |
}else if (cmdDetails.details.increase){ | |
increaseList(list,who) | |
}else if (cmdDetails.details.decrease){ | |
decreaseList(list,who) | |
}else{ | |
showHelp(who); | |
} | |
}, | |
// handles playlist actions, start/stop/fadein/fadeout | |
trackHandler = function(cmdDetails,trackDetails,who,restrict){ | |
if (debug){ | |
log("Track Handler Action:" + cmdDetails.action) | |
log("Track Handler Track:" + trackDetails.title) | |
log("Track Handler Mode:" + trackDetails.mode) | |
} | |
if (restrict && trackDetails.access == 'gm'){ | |
sendChat(who,'Invalid Roll20AM Command. Restricted action not allowed by a player.',null,{noarchive:true}); | |
return; | |
} | |
//play depending on mode | |
if (cmdDetails.details.play){ | |
if (trackDetails.mode == 'loop'){ | |
playTrack(trackDetails.id,trackDetails.volume,'loop',who); | |
} | |
if (trackDetails.mode == 'single'){ | |
playTrack(trackDetails.id,trackDetails.volume,null,who); | |
} | |
} | |
if (cmdDetails.details.stop){ | |
if (cmdDetails.details.ignore){ | |
} else { | |
stopTrack(trackDetails.id,who); | |
} | |
} | |
if (cmdDetails.details.fade){ | |
if (cmdDetails.details.in){ | |
fadeInTrack(trackDetails.id,trackDetails.volume,trackDetails.fadetime,who) | |
} else if (cmdDetails.details.out){ | |
fadeOutTrack(trackDetails.id,trackDetails.volume,0,trackDetails.fadetime,cmdDetails.details.nomenu,who) | |
} | |
} | |
if (cmdDetails.details.increase){ | |
increaseTrack(trackDetails.id,who) | |
} | |
if (cmdDetails.details.decrease){ | |
decreaseTrack(trackDetails.id,who) | |
} | |
}, | |
configHandler = function(cmdDetails,who,restrict){ | |
if (debug){ | |
log("Config Handler Action:" + cmdDetails.action) | |
} | |
if (cmdDetails.details.import){ | |
importJukebox(who); | |
}else if (cmdDetails.details.remove){ | |
removeJukebox(who); | |
}else if (cmdDetails.details.viewBy){ | |
setAccess(cmdDetails.details.viewBy,null,who); | |
}else if (cmdDetails.details.access){ | |
setAccess(null,cmdDetails.details.access,who); | |
}else if (cmdDetails.details.display){ | |
state.Roll20AM.nomenu = cmdDetails.details.display | |
}else { | |
state.Roll20AM.menu = cmdDetails.details.menu | |
outputConfig(cmdDetails.details.menu,who,cmdDetails.details.filter); | |
} | |
}, | |
editHandler = function(cmdDetails,list,trackDetails,who,restrict){ | |
var mode,access,tag1,tag2,tag3,tag4,tag5 | |
if (debug){ | |
log("Edit Handler Action:" + cmdDetails.action) | |
if (list){ | |
log("Edit Handler List:" + list.name) | |
} | |
if (trackDetails){ | |
log("Edit Handler Track:" + trackDetails.title) | |
} | |
} | |
if (cmdDetails.details.mode){ | |
if (cmdDetails.details.random){ | |
if (cmdDetails.details.single){ | |
mode = 'randomSingle' | |
} else if (cmdDetails.details.loop){ | |
mode = 'randomLoop' | |
} | |
} else if (cmdDetails.details.single){ | |
mode = cmdDetails.details.single | |
} else if (cmdDetails.details.loop){ | |
mode = cmdDetails.details.loop | |
} else if (cmdDetails.details.shuffle){ | |
mode = cmdDetails.details.shuffle | |
} else { | |
mode = cmdDetails.details.together | |
} | |
} | |
if (cmdDetails.details.set){ | |
if (cmdDetails.details.tag1){ | |
tag1 = 'On' | |
} | |
if (cmdDetails.details.tag2){ | |
tag2 = 'On' | |
} | |
if (cmdDetails.details.tag3){ | |
tag3 = 'On' | |
} | |
if (cmdDetails.details.tag4){ | |
tag4 = 'On' | |
} | |
} | |
if (cmdDetails.details.unset){ | |
if (cmdDetails.details.tag1){ | |
tag1 = 'Off' | |
} | |
if (cmdDetails.details.tag2){ | |
tag2 = 'Off' | |
} | |
if (cmdDetails.details.tag3){ | |
tag3 = 'Off' | |
} | |
if (cmdDetails.details.tag4){ | |
tag4 = 'Off' | |
} | |
} | |
if (cmdDetails.details.access){ | |
if (cmdDetails.details.player){ | |
access = cmdDetails.details.player | |
} else { | |
access = cmdDetails.details.gm | |
} | |
} | |
if (!list && !trackDetails){ | |
if (cmdDetails.details.volume){ | |
changeVolumeMaster(cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.delayed){ | |
changeDelayMaster(cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.fade){ | |
if (cmdDetails.details.in){ | |
changeFadeInMaster(cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.out){ | |
changeFadeOutMaster(cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.time){ | |
changeFadeTimeMaster(cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.access){ | |
edit(null,null,null,mode,access,who) | |
} | |
} | |
}else if (list){ | |
if (cmdDetails.details.volume){ | |
changeVolumeList(list,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.delayed){ | |
changeDelayList(list,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.mode){ | |
edit(list,null,null,mode,access,who) | |
} | |
if (cmdDetails.details.access){ | |
edit(list,null,null,mode,access,who) | |
} | |
if (cmdDetails.details.remove){ | |
edit(list,null,cmdDetails.details.remove,null,null,who) | |
} | |
if (cmdDetails.details.set){ | |
edit(list,null,cmdDetails.details.set,null,null,tag1,tag2,tag3,tag4,who) | |
} | |
if (cmdDetails.details.unset){ | |
edit(list,null,cmdDetails.details.unset,null,null,tag1,tag2,tag3,tag4,who) | |
} | |
if (cmdDetails.details.fade){ | |
if (cmdDetails.details.in){ | |
changeFadeInList(list,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.out){ | |
changeFadeOutList(list,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.time){ | |
changeFadeTimeList(list,cmdDetails.details.level,who); | |
} | |
} | |
}else if (trackDetails){ | |
if (cmdDetails.details.volume){ | |
changeVolumeTrack(trackDetails.id,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.delayed){ | |
changeDelayTrack(trackDetails.id,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.mode){ | |
edit(null,trackDetails.id,null,mode,access,who) | |
} | |
if (cmdDetails.details.access){ | |
edit(null,trackDetails.id,null,mode,access,who) | |
} | |
if (cmdDetails.details.remove){ | |
edit(null,trackDetails.id,cmdDetails.details.remove,mode,access,who) | |
} | |
if (cmdDetails.details.unset){ | |
edit(null,trackDetails.id,cmdDetails.details.unset,null,null,tag1,tag2,tag3,tag4,who) | |
} | |
if (cmdDetails.details.set){ | |
edit(null,trackDetails.id,cmdDetails.details.set,null,null,tag1,tag2,tag3,tag4,who) | |
} | |
if (cmdDetails.details.fade){ | |
if (cmdDetails.details.in){ | |
changeFadeInTrack(trackDetails.id,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.out){ | |
changeFadeOutTrack(trackDetails.id,cmdDetails.details.level,who); | |
} | |
if (cmdDetails.details.time){ | |
changeFadeTimeTrack(trackDetails.id,cmdDetails.details.level,who); | |
} | |
} | |
}else{ | |
showHelp(msg.playerid); | |
} | |
}, | |
//****************************************************************************** | |
//Common Functions | |
//****************************************************************************** | |
getPlayList = function(listName){ | |
return state.Roll20AM.playLists[listName]; | |
}, | |
getTrackDetails = function(trackID){ | |
return _.findWhere(state.Roll20AM.trackDetails,{id:trackID}); | |
}, | |
getTrackID = function(trackTitle){ | |
return state.Roll20AM.trackDetails[trackTitle].id; | |
}, | |
getJukeBox = function(trackID){ | |
return getObj('jukeboxtrack',trackID); | |
}, | |
//****************************************************************************** | |
//Special - Stop all tracks | |
//****************************************************************************** | |
stopAll = function(who){ | |
var jbTrack | |
if (debug){ | |
log('Stopping All Tracks') | |
} | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
if (list.playing){ | |
if (debug){ | |
log('Stopping List:' + list) | |
} | |
list.playing = false; | |
list.currentTrack = []; | |
} | |
}) | |
_.each(state.Roll20AM.trackDetails,(track)=>{ | |
if (track.playing){ | |
if (debug){ | |
log('Stopping Track:' + track.id) | |
} | |
stopTrack(track.id,who) | |
} | |
}); | |
}, | |
//****************************************************************************** | |
//Audio Functions | |
// - Play, Stop, Fade In, Fade Out, Increase, Decrease | |
// - Updates the List volume, Associated Tracks and JukeBox | |
// - state.Roll20AM.playlists, state.Roll20AM.tradkDetails | |
//****************************************************************************** | |
increaseList = function(list,who){ | |
var level | |
if (debug){ | |
log('Increase List:'+list.name) | |
} | |
level = list.volume*1 + 5 | |
list.volume = level; | |
_.each(list.trackids,(trackID)=>{ | |
increaseTrack(trackID,who) | |
}); | |
}, | |
decreaseList = function(list,who){ | |
if (debug){ | |
log('Decreasing List Volume') | |
log('Decrease List:'+list.name) | |
} | |
var newVolume, track | |
newVolume = list.volume*1 - 5 | |
list.volume = newVolume | |
_.each(list.trackids,(trackID)=>{ | |
decreaseTrack(trackID,who) | |
}); | |
}, | |
fadeListIn = function(list,who){ | |
if(debug){ | |
log('Fade List Name:' + list.name) | |
} | |
//start the next track in playlist with a volume of 0, so we can fade in | |
if (list.mode == 'loop'){ | |
playListInOrder(list,0,who); | |
} | |
if (list.mode == 'randomSingle' || list.mode == 'randomLoop'){ | |
playListRandom(list,0,who); | |
} | |
if (list.mode == 'shuffle'){ | |
playListShuffle(list,0,who); | |
} | |
if (list.mode == 'single'){ | |
playListInOrder(list,0,who); | |
} | |
if (list.mode == 'together'){ | |
playListTogether(list,0,who); | |
} | |
//refresh the list variable to get current playing track | |
list = getPlayList(list.name); | |
//fade in the track | |
_.each(list.currentTrack,(trackID)=>{ | |
fadeInTrack(trackID,list.volume,list.fadetime,who) | |
}) | |
}, | |
fadeListOut = function(list,nomenu,who){ | |
var trackDetails | |
// verify that the list is actualy playing | |
if(!list.playing){ | |
sendChat('Roll20AM','/w "'+who+'" In order to fade a list, it must be playing.',null,{noarchive:true}) | |
return; | |
} | |
if(debug){ | |
log('Fade List Name:' + list.name) | |
} | |
list.playing = false | |
//get current playing song(s) and fade out | |
_.each(list.currentTrack,(trackID)=>{ | |
trackDetails = getTrackDetails(trackID) | |
if (trackDetails.playing){ | |
fadeOutTrack(trackID,trackDetails.volume,0,trackDetails.fadetime,nomenu,who) | |
} | |
}) | |
}, | |
//plays tracks in sequential order | |
playListInOrder = function(list,startVolume,who){ | |
if (debug){ | |
log('Playing List (In Order):'+list.name) | |
} | |
//Set list playing to true, used later to determine lists that are active | |
list.playing = true; | |
playNextTrack(list,list.trackids,startVolume,who) | |
}, | |
//plays tracks randomly | |
playListRandom = function(list,startVolume,who){ | |
if (debug){ | |
log('Playing Random List:'+list.name) | |
} | |
var trackID; | |
//Set list playing to true, used later to determine lists that are active. Since random, we aren't tracking current played tracks | |
list.playing = true; | |
list.currentTrack = []; | |
trackID = list.trackids[randomInteger(list.trackids.length)- 1] | |
playNextTrack(list,[trackID],startVolume,who) | |
}, | |
//plays tracks randomly | |
playListShuffle = function(list,startVolume,who){ | |
if (debug){ | |
log('Playing Shuffle List:'+list.name) | |
} | |
//Set list playing to true, used later to determine lists that are active | |
if (!list.playing){ | |
list.shuffleIds = [] | |
} | |
list.playing = true; | |
if (list.shuffleIds.length == 0){ | |
if (debug){ | |
log('Creating Shuffle') | |
} | |
//Copy trackids into shuffe tracks | |
_.each(list.trackids,(t)=>{ | |
list.shuffleIds.push(t); | |
}); | |
//Use Durstenfeld method of shuffling list | |
for (var i = list.shuffleIds.length - 1; i > 0; i--) { | |
var j = Math.floor(Math.random() * (i + 1)); | |
var temp = list.shuffleIds[i]; | |
list.shuffleIds[i] = list.shuffleIds[j]; | |
list.shuffleIds[j] = temp; | |
} | |
} | |
playNextTrack(list,list.shuffleIds,startVolume,who) | |
}, | |
//plays tracks randomly | |
playListTogether = function(list,startVolume,who){ | |
if (debug){ | |
log('Playing Together List:'+list.name) | |
} | |
var volume, trackID | |
//Set list playing to true, used later to determine lists that are active | |
list.playing = true; | |
//Set start volume | |
if (startVolume!=0){ | |
volume = list.volume | |
}else{ | |
volume = startVolume; | |
} | |
if (debug){ | |
log('Start Volume:'+volume) | |
} | |
//Play all songs at once. Push all songs into currentTrack | |
_.each(list.trackids, (trackID) =>{ | |
list.currentTrack.push(trackID); | |
playTrack(trackID,volume,null,who); | |
}) | |
}, | |
//stops the indicated tracks from playing | |
stopList = function(list,who){ | |
if (debug){ | |
log('Stopping:' + list.name) | |
} | |
//stop list and clear current tracks | |
list.playing = false; | |
_.each(list.currentTrack,(trackID)=>{ | |
if (debug){ | |
log('Stopping:' + trackID) | |
} | |
stopTrack(trackID,who) | |
}); | |
list.currentTrack = []; | |
}, | |
//this takes input from one of the modes, find next track to play and plays it | |
playNextTrack = function(list,listTracks,startVolume,who){ | |
if (debug){ | |
log('Playing Next Track List:'+list.name) | |
} | |
//initialize | |
var found = false, nexttrackID, trackID, volume | |
//find next track to play | |
if (list.currentTrack.length > 0){ | |
//get each track from the playlist tracks | |
_.each(listTracks,(trackID)=>{ | |
//if the track isn't in the current playlistm play it, Found switch makes next track is used | |
if (list.currentTrack.indexOf(trackID) == -1){ | |
if (!found){ | |
nexttrackID = trackID | |
if (debug){ | |
log('Next Track:' + nexttrackID) | |
} | |
found = true; | |
} | |
} | |
}); | |
}; | |
//if entire play tracks are in current tracks, all have played, wipe out current tracks and get first one | |
if (!found){ | |
list.currentTrack = [] | |
nexttrackID = listTracks[0] | |
if (debug){ | |
log('First Track:' + nexttrackID) | |
} | |
} | |
//push this track into the current track list | |
list.currentTrack.push(nexttrackID); | |
// volume may have been set to 0 by fading, reset to playlist or master volume. For fade in, we want start volume at 0 | |
if (startVolume!=0){ | |
volume = list.volume | |
}else{ | |
volume = startVolume; | |
} | |
playTrack(nexttrackID,volume,null,who); | |
}, | |
//plays the track. | |
playTrack = function(trackID,level,loop,who){ | |
var jbTrack=getJukeBox(trackID), trackDetails=getTrackDetails(trackID) | |
//set playing to true in Track Details, get Jukebox Object and set to playing | |
if (debug){ | |
log("Starting Track:" + trackID) | |
log('Start Volume:'+level) | |
} | |
_.each(state.Roll20AM.playLists,list =>{ | |
if (list.trackids.indexOf(trackID) >= 0){ | |
list.playing = true | |
} | |
}) | |
trackDetails.playing = true; | |
trackDetails.who = who; | |
jbTrack.set({playing:true,softstop:false,volume:level}); | |
if (displayTrack){ | |
sendChat(who,'Now Playing:' + trackDetails.title ,null,{noarchive:true}); | |
} | |
}, | |
//stops the track. Either Track ID or Track Title will come into this function | |
stopTrack = function(trackID,extra){ | |
var jbTrack=getJukeBox(trackID),trackDetails=getTrackDetails(trackID) | |
_.each(state.Roll20AM.playLists,list =>{ | |
if (list.playing){ | |
if (list.trackids.indexOf(trackID) >= 0){ | |
list.playing = false | |
} | |
} | |
}) | |
//set playing to true in Track Details, get Jukebox Object and set to playing | |
trackDetails.playing=false; | |
jbTrack.set({playing:false,softstop:true,loop:false}); | |
}, | |
//fade in starts at zero an incrementally increases volume to the fade in volume | |
fadeInTrack = function(trackID,target,seconds,who){ | |
var jbTrack=getJukeBox(trackID),trackDetails=getTrackDetails(trackID),levelChange,level,timer | |
//calc the volume change per second | |
level = 0 | |
levelChange = +target / +seconds; | |
if(debug){ | |
log("Target:" + target) | |
log("Time:" + seconds) | |
log("Volume Change:" + levelChange) | |
} | |
// If track is being played outside of a list, start the track | |
if (!trackDetails.playing){ | |
trackDetails.playing = true | |
playTrack(trackID,0,null,who) | |
} | |
//increment volume every 1 1/2 seconds until volume is at fade up volume. ClearTimerout stops the setinterval function | |
timer = setInterval(function(){ | |
log("New Volume:" + level) | |
level = level + levelChange*1; | |
if (+level <= +target){ | |
jbTrack.set({volume:level}); | |
}else{ | |
log('Clearing Timeout') | |
clearTimeout(timer) | |
} | |
}, 1500 ); | |
}, | |
//fade out starts at fade in volunme an incrementally decreases volume to the fade out volume | |
fadeOutTrack = function(trackID,level,target,seconds,nomenu,who){ | |
var jbTrack=getJukeBox(trackID), trackDetails=getTrackDetails(trackID), levelChange, timer | |
// verify that the list is actualy playing | |
if(!trackDetails.playing){ | |
sendChat('Roll20AM','/w "'+who+'" In order to fade a track, it must be playing.',null,{noarchive:true}) | |
return; | |
} | |
if(debug){ | |
log('Fade Out Track Name:' + trackID) | |
log('Fade Target' + target) | |
log('Fade Time' + seconds) | |
} | |
//calc the volume change per second | |
levelChange = (+level - target*1) / seconds; | |
if(debug){ | |
log("Volume Change:" + levelChange) | |
} | |
timer = setInterval(function(){ | |
if(debug){ | |
log("New Volume:" + level) | |
} | |
level = +level - levelChange; | |
//Decrement volume every 1 1/2 seconds until volume is at fade down volume. ClearTimerout stops the setinterval function. Output config refreshes the play button | |
if ( +level > target){ | |
jbTrack.set({volume:level}); | |
}else{ | |
if(debug){ | |
log('Clearing Timer') | |
} | |
stopTrack(trackID,null) | |
clearTimeout(timer) | |
if (!nomenu){ | |
outputConfig(state.Roll20AM.menu,who); | |
} | |
} | |
}, 1500 ); | |
}, | |
//****************************************************************************** | |
//Edit Master Functions | |
// - Modify the master volume, playlists and tracks assigned to playlist | |
// - Volume, Fade In, Fade Out, Fade Time | |
// - Update the approprate element | |
//****************************************************************************** | |
changeVolumeMaster = function(level){ | |
if (debug){ | |
log('Change Master Volume:'+level) | |
} | |
state.Roll20AM.masterVolume = parseInt(level) | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
changeVolumeList(list,parseInt(level)) | |
}); | |
}, | |
changeFadeInMaster = function(level){ | |
if (debug){ | |
log('Change Fade In Master Volume:'+level) | |
} | |
state.Roll20AM.fadeVolumeUp = level | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
changeFadeInList(list,level) | |
}); | |
}, | |
changeFadeOutMaster = function(level){ | |
if (debug){ | |
log('Change Fade Out Volume:'+level) | |
} | |
state.Roll20AM.fadeVolumeDown = level | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
changeFadeOutList(list,level) | |
}); | |
}, | |
changeFadeTimeMaster = function(level){ | |
if (debug){ | |
log('Change Fade Time Seconds:'+level) | |
} | |
state.Roll20AM.fadeTime = level | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
changeFadeTimeList(list,level) | |
}); | |
}, | |
changeDelayMaster = function(level){ | |
if (debug){ | |
log('Change Delay Seconds:'+level) | |
} | |
state.Roll20AM.delay = level | |
_.each(state.Roll20AM.playLists,(list)=>{ | |
changeDelayList(list,level) | |
}); | |
}, | |
//************************************************************************** | |
//Edit List Functions | |
// - Modify the volume in the playlists and tracks assigned to playlist | |
// - Volume, Fade In, Fade Out, Fade Time | |
// - Update the approprate element | |
//************************************************************************** | |
changeVolumeList = function(list,level){ | |
if (debug){ | |
log('Changing List:' + list.name) | |
log('Change List Volume:'+level) | |
} | |
list.volume = level; | |
_.each(list.trackids,(trackID)=>{ | |
changeVolumeTrack(trackID,level) | |
}) | |
}, | |
changeFadeInList = function(list,level){ | |
if (debug){ | |
log('Change Fade In List Volume:'+level) | |
} | |
list.fadeup = level; | |
_.each(list.trackids,(trackID)=>{ | |
changeFadeInTrack(trackID,level) | |
}) | |
}, | |
changeFadeOutList = function(list,level){ | |
if (debug){ | |
log('Change Fade Out List Volume:'+level) | |
} | |
list.fadedown = level; | |
_.each(list.trackids,(trackID)=>{ | |
changeFadeOutTrack(trackID,level) | |
}) | |
}, | |
changeFadeTimeList = function(list,level){ | |
if (debug){ | |
log('Change Fade Time List Seconds:'+level) | |
} | |
list.fadetime = level; | |
_.each(list.trackids,(trackID)=>{ | |
changeFadeTimeTrack(trackID,level) | |
}) | |
}, | |
changeDelayList = function(list,level){ | |
if (debug){ | |
log('Change Delay List Seconds:'+level) | |
} | |
list.delay = level; | |
_.each(list.trackids,(trackID)=>{ | |
changeDelayTrack(trackID,level) | |
}) | |
}, | |
//****************************************************************************** | |
//Edit Track Functions | |
// - Modify the volume in the trackDetails and in some cases, the Jukebox Track | |
// - Volume, Fade In, Fade Out, Fade Time, Increase, Decrease | |
// - Pull the details from state.Roll20AM.trackDetails and sometimes Jukebox | |
// - Update the approprate element | |
//****************************************************************************** | |
changeVolumeTrack = function(trackID,level){ | |
var trackDetails = getTrackDetails(trackID) | |
if (debug){ | |
log('Change Track:'+trackID) | |
log('Change Track Volume:'+level) | |
} | |
trackDetails.volume = level | |
}, | |
changeFadeInTrack = function(trackID,level){ | |
var trackDetails = getTrackDetails(trackID) | |
if (debug){ | |
log('Change Fade In Track:'+trackID) | |
log('Change Fade In Track Volume:'+level) | |
} | |
trackDetails.fadeup = level; | |
}, | |
changeFadeOutTrack = function(trackID,level){ | |
var trackDetails = getTrackDetails(trackID) | |
if (debug){ | |
log('Change Fade Out Track:'+trackID) | |
log('Change Fade Out Track Volume:'+level) | |
} | |
trackDetails.fadedown = level; | |
}, | |
changeFadeTimeTrack = function(trackID,level){ | |
var trackDetails = getTrackDetails(trackID) | |
if (debug){ | |
log('Change Fade Time Track:'+trackID) | |
log('Change Fade Time Track Seconds:'+level) | |
} | |
trackDetails.fadetime = level; | |
}, | |
changeDelayTrack = function(trackID,level){ | |
var trackDetails = getTrackDetails(trackID) | |
if (debug){ | |
log('Change Delay Track:'+trackID) | |
log('Change Delay Track Seconds:'+level) | |
} | |
trackDetails.delay = level; | |
}, | |
increaseTrack = function(trackID,who){ | |
var level, trackDetails=getTrackDetails(trackID),jbTrack=getJukeBox(trackID) | |
if (debug){ | |
log('Increase Track:'+trackDetails.title) | |
} | |
level = trackDetails.volume*1 + 5 | |
trackDetails.volume = level | |
jbTrack.set({volume:level}); | |
}, | |
decreaseTrack = function(trackID,who){ | |
var level, trackDetails=getTrackDetails(trackID),jbTrack=getJukeBox(trackID) | |
if (debug){ | |
log('Increase Track:'+trackDetails.title) | |
} | |
level = trackDetails.volume*1 - 5 | |
trackDetails.volume = level | |
jbTrack.set({volume:level}); | |
}, | |
//Import everything from Jukebox | |
removeJukebox = function(who){ | |
state.Roll20AM.playLists = {} | |
state.Roll20AM.trackDetails = {} | |
sendChat(who,'All Playlists and Tracks have been removed from Roll20AM',null,{noarchive:true}); | |
}, | |
//Import everything from Jukebox | |
importJukebox = function(who){ | |
var shuffleState = {'s':'shuffle','a':'together','b':'single'}; | |
var lists = JSON.parse(Campaign().get('jukeboxfolder')); | |
var tagName,track,title,unique,trackID,trackDetails | |
if (debug){ | |
log('Importing Tracks') | |
} | |
_.each(findObjs({type:'jukeboxtrack'}),(track)=>{ | |
title = track.get('title') | |
trackID = track.get('_id') | |
if (state.Roll20AM.trackDetails[title] == undefined){ | |
state.Roll20AM.trackDetails[title] = { | |
id:trackID, | |
title:title, | |
playing:false, | |
access:'gm', | |
mode:'single', | |
delay:0, | |
tags:['Off','Off','Off','Off','Off'], | |
volume:state.Roll20AM.masterVolume, | |
fadeup:state.Roll20AM.fadeVolumeUp, | |
fadedown:state.Roll20AM.fadeVolumeDown, | |
fadetime:state.Roll20AM.fadeTime, | |
who:null | |
} | |
} | |
}) | |
_.each(lists,list=>{ | |
if (list.n != undefined){ | |
importList(list.n) | |
_.each(list.i,(t)=>{ | |
track = getJukeBox(t) | |
if (track != undefined){ | |
title = track.get('title') | |
trackID = getTrackID(title) | |
if (state.Roll20AM.playLists[list.n].trackids.indexOf(trackID) == -1){ | |
state.Roll20AM.playLists[list.n].trackids.push(trackID) | |
} | |
} | |
}) | |
} | |
}) | |
importList("Tag1") | |
importList("Tag2") | |
importList("Tag3") | |
importList("Tag4") | |
sendChat(who,'All Playlists and Tracks have been imported',null,{noarchive:true}); | |
}, | |
importList = function(listName,who){ | |
if (debug){ | |
log('Importing List:'+listName) | |
} | |
if (!state.Roll20AM.playLists[listName]){ | |
state.Roll20AM.playLists[listName] = { | |
name:listName, | |
trackids:[], | |
shuffleIds:[], | |
playing:false, | |
currentTrack:[], | |
lastTrack:null, | |
access:'gm', | |
mode:'loop', | |
delay:0, | |
volume:state.Roll20AM.masterVolume, | |
fadeup:state.Roll20AM.fadeVolumeUp, | |
fadedown:state.Roll20AM.fadeVolumeDown, | |
fadetime:state.Roll20AM.fadeTime | |
} | |
} | |
}, | |
// checkListDuplicates = function(listName,trackID,trackTitle,who){ | |
// var unique,trackCheck,titleCheck | |
// unique = true; | |
// _.each(state.Roll20AM.playLists[listName].trackids,(trackCheckID)=>{ | |
// trackCheck = getJukeBox(trackCheckID) | |
// titleCheck = trackCheck.get('title') | |
// if (trackTitle == titleCheck && trackID != trackCheckID){ | |
// if (debug){ | |
// log("Duplicate Found:" +titleCheck) | |
// } | |
// unique = false; | |
// } | |
// }) | |
// return unique | |
// }, | |
setAccess = function(viewBy,access,who){ | |
if (viewBy){ | |
state.Roll20AM.API = viewBy | |
} | |
if (access){ | |
state.Roll20AM.access = access | |
if (access == 'None'){ | |
_.each(state.Roll20AM.playLists,list=>{ | |
edit(list,null,null,null,'gm',null,null,null,null,who) | |
_.each(list.trackids,track=>{ | |
edit(null,track,null,null,'gm',null,null,null,null,who) | |
}); | |
}); | |
} | |
if (access == 'Full'){ | |
_.each(state.Roll20AM.playLists,list=>{ | |
edit(list,null,null,null,'player',null,null,null,null,who) | |
_.each(list.trackids,track=>{ | |
edit(null,track,null,null,'player',null,null,null,null,who) | |
}); | |
}); | |
} | |
} | |
}, | |
//Edit the Playlist | |
edit = function(list,trackID,action,mode,access,tag1,tag2,tag3,tag4,who){ | |
var trackDetails=getTrackDetails(trackID),list,trackAccess | |
if(debug){ | |
if (list){ | |
log('Edit List Name:' + list.name) | |
} | |
if (trackDetails){ | |
log('Edit Track Name:' + trackDetails.title) | |
} | |
log('Edit Mode:' + mode) | |
log('Edit Access:' + access) | |
log('Edit Action:' + action) | |
log('Tag1:' + tag1) | |
log('Tag2:' + tag2) | |
log('Tag3:' + tag3) | |
log('Tag4:' + tag4) | |
} | |
if (!list && !trackDetails){ | |
state.Roll20AM.API = access | |
} | |
if (list){ | |
list.mode = mode || list.mode; | |
list.access = access || list.access; | |
if (access){ | |
_.each(list.trackids,trackAccessID=>{ | |
trackAccess = getTrackDetails(trackAccessID) | |
trackAccess.access = access | |
}); | |
} | |
if (action == 'remove'){ | |
delete state.Roll20AM.playLists[list.name] | |
} | |
} | |
if (trackDetails){ | |
trackDetails.mode = mode || trackDetails.mode; | |
trackDetails.access = access || trackDetails.access; | |
if (action == 'set' || action == 'unset'){ | |
if (tag1){ | |
trackDetails.tags[0] = tag1 | |
if (tag1 == "On"){ | |
list = getPlayList('Tag1') | |
if (!list.trackids[trackDetails.id]){ | |
list.trackids.push(trackDetails.id) | |
} | |
}else{ | |
list = getPlayList('Tag1') | |
list.trackids.splice(list.trackids.indexOf(trackDetails.id),1); | |
} | |
} | |
if (tag2){ | |
trackDetails.tags[1] = tag2 | |
if (tag2 == "On"){ | |
list = getPlayList('Tag2') | |
if (!list.trackids[trackDetails.id]){ | |
list.trackids.push(trackDetails.id) | |
} | |
}else{ | |
list = getPlayList('Tag2') | |
list.trackids.splice(list.trackids.indexOf(trackDetails.id),1); | |
} | |
} | |
if (tag3){ | |
trackDetails.tags[2] = tag3 | |
if (tag3 == "On"){ | |
list = getPlayList('Tag3') | |
if (!list.trackids[trackDetails.id]){ | |
list.trackids.push(trackDetails.id) | |
} | |
}else{ | |
list = getPlayList('Tag3') | |
list.trackids.splice(list.trackids.indexOf(trackDetails.id),1); | |
} | |
} | |
if (tag4){ | |
trackDetails.tags[3] = tag4 | |
if (tag4 == "On"){ | |
list = getPlayList('Tag4') | |
if (!list.trackids[trackDetails.id]){ | |
list.trackids.push(trackDetails.id) | |
} | |
}else{ | |
list = getPlayList('Tag4') | |
list.trackids.splice(list.trackids.indexOf(trackDetails.id),1); | |
} | |
} | |
} | |
if (action == 'remove'){ | |
delete state.Roll20AM.trackDetails[trackDetails.id]; | |
_.each(state.Roll20AM.playLists,(L)=>{ | |
L.trackids.splice(L.trackids.indexOf(trackDetails.id),1); | |
}); | |
} | |
} | |
}, | |
//sets the indicated preference | |
setPref = function(details){ | |
state.Roll20AM.restrict = details.restrict || state.Roll20AM.restrict; | |
state.Roll20AM.API = details.API || state.Roll20AM.API; | |
state.Roll20AM.tag = details.tag || state.Roll20AM.tag; | |
}, | |
//stores new tracks into the state with default volume and loop state | |
// storeTracks = function(track){ | |
// if(track){ | |
// state.Roll20AM.trackDetails[track.id] = { | |
// volume:track.get('volume')/state.Roll20AM.masterVolume, | |
// }; | |
// }else{ | |
// _.each(findObjs({type:'jukeboxtrack'}),(t)=>{ | |
// if(!_.contains(state.Roll20AM.trackids,t.id)){ | |
// state.Roll20AM.trackDetails[t.id] = { | |
// volume:t.get('volume')/state.Roll20AM.masterVolume, | |
// }; | |
// } | |
// }); | |
// } | |
// }, | |
//Returns the first jukeboxtrack to match the provided trackname (and the restriction tag if told to restrict) | |
// getTracks = function(trackNames,restrict,ignore){ | |
// return filterObjs((o)=>{ | |
// if(trackNames.length>0){ | |
// if(ignore){ | |
// if(restrict){ | |
// return(o.get('type')==='jukeboxtrack' && _.every(trackNames,(i)=>{return o.get('title').replace(state.Roll20AM.tag,'').trim()!==i && o.get('title').indexOf(state.Roll20AM.tag)>-1;})); | |
// }else{ | |
// return(o.get('type')==='jukeboxtrack' && _.every(trackNames,(i)=>{return o.get('title').replace(state.Roll20AM.tag,'').trim()!==i;})); | |
// } | |
// }else{ | |
// if(restrict){ | |
// return(o.get('type')==='jukeboxtrack' && _.some(trackNames,(i)=>{return o.get('title').replace(state.Roll20AM.tag,'').trim()===i && o.get('title').indexOf(state.Roll20AM.tag)>-1;})); | |
// }else{ | |
// return(o.get('type')==='jukeboxtrack' && _.some(trackNames,(i)=>{return o.get('title').replace(state.Roll20AM.tag,'').trim()===i;})); | |
// } | |
// } | |
// }else{return false;} | |
// }); | |
// }, | |
showHelp = function(who){ | |
sendChat('Roll20AM','/w "'+who+'"' | |
+'<div style="border: 2px solid black; background-color: white; padding: 3px 3px;">' | |
+'<div style="border-bottom: 4px solid black;">' | |
+'<div style="font-weight: bold;font-size: 130%;">' | |
+'Roll20AM v'+version+' -- Hear the dice, hear the action!' | |
+'</div></div>' | |
+'For all the details on Roll20AM, please see the <u><b>[Roll20AM handout]('+helpLink+')</b></u>.',null,{noarchive:true} | |
); | |
}, | |
//Generates the help handout | |
//Need to make distinction between jukebox and script playlists more clear | |
generateHelp = function(){ | |
var help = getObj('handout',state.Roll20AM.help), | |
playerHelp = '<div style="border: 2px solid black; background-color: white; padding: 3px 3px;">' | |
+'<div style="border-bottom: 4px solid black;">' | |
+'<div style="font-weight: bold;font-size: 130%;">' | |
+version+' Roll20AM -- Hear the dice, hear the action!' | |
+'</div>' | |
+"The Roll20 Audio Master's goal is to make the jukebox easier to use and more powerful. The script allows you to play, stop, and adjust the volume of tracks " | |
+'via chat commands (and therefore macros). The script provides all the functionality that the standard jukebox interface does, plus the ability to ' | |
+'manipulate your tracks in several ways that were not possible before. This help menu is separated into two parts. The top of the handout is a basic ' | |
+'overview of the command syntax as well as those commands that are accessible by players. The GM Notes section of the handout describes the commands ' | |
+'that are GM only as well as covering the chat menu interface which is also only available to the GM.' | |
+'</div>' | |
+'<div style="padding-left:10px;margin-bottom:3px;font-weight: bold;font-size: 110%">' | |
+'Basic Syntax' | |
+'</div>' | |
+'<div style="padding-left:10px;margin-bottom:3px;">' | |
+'All Roll20AM commands use the same basic syntax. The contents of a command will change based on what you are doing, but the basic format will always look like this:' | |
+'<ul><li style=>' | |
+'<b><span style="font-family: serif;">!roll20AM --action,action settings, or options|track name to act on|or playlist name to act on</span></b> ' | |
+'<ul>' | |
+'&'+'#8226'+'; Action keywords are preceded by a space followed by a double dash " --". The action keywords are: play, stop, listCreate, listEdit, vcontrol, delayCancel, and config.<br>' | |
+'&'+'#8226'+'; Action keywords and settings/options are separted by a comma. The option keywords will be described below in their associated action keyword section.<br>' | |
+'&'+'#8226'+'; For most actions, the settings/options are optional.<br>' | |
+'&'+'#8226'+'; Track/playlist names are always separated by a bar "|". <br>' | |
+'&'+'#8226'+'; Commands can be chained together by making a second (third, fourth, etc) action group. Action groups are acted on sequentially.' | |
+'</ul>' | |
+'</li></div>' | |
+'<div style="padding-left:10px;margin-bottom:3px;">' | |
+'<div style="font-weight: bold;font-size: 110%">' | |
+'Player Accessible Action Keywords' | |
+'</div>' | |
+'Commands are shown with all possible settings. If a setting is optional, it will be enclosed in brackets. If only one of a series of settings will ' | |
+'be accepted, they are separated by a slash "/". The order of options does not matter as long as the action keyword begins the action group and the list of tracks ends the action group.' | |
+'<div style="padding-left:10px;margin-bottom:3px;">' | |
+'<ul><li style="border-top: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--play,[swap],[loop],[mode:shuffle/trueRandom/together/single],[delay:<i>seconds</i>]|track/list to play|track/list to play|...</span></b><br> ' | |
+'Plays the indicated track(s) or playlist(s). When sent by a player, only those tracks/playlists that are tagged as player accessible will be played.' | |
+'<ul>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">swap - </span></b>All other tracks will be stopped when the indicated tracks/lists begin playing.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">loop - </span></b>The indicated tracks will loop. When playlists are specified, only applies when ' | |
+'the together option is indicated and then all tracks in the list will loop.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">shuffle - </span></b>No effect on individual tracks. Will cause the indicated playlists to shuffle when played.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">trueRandom - </span></b>As shuffle, but the same track may be played repeatedly.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">together - </span></b>Plays all tracks in the indicated playlist(s) at once.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">delay:<i>seconds</i> - </span></b>Delays the execution of the action group by this many seconds.<br>' | |
+'</ul>' | |
+'</li></ul>' | |
+'<ul><li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--stop,[soft],[ignore],[delay:<i>seconds</i>]|track/list to stop|track/list to stop|...</span></b><br> ' | |
+'Stops the indicated track(s) or playlist(s). When sent by a player, only those tracks/playlists that are tagged as player accessible will be stopped.' | |
+'<ul>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">soft - </span></b>Allows the indicated track(s)/playlist(s) to finish their current playthrough before stopping them.<br>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">ignore - </span></b>Inverts the command, stopping all tracks/playlists except those listed. If no track or playlist is listed, stops all tracks/playlists (respects player restrictions).<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">delay:<i>seconds</i> - </span></b>Delays the execution of the action group by this many seconds.<br>' | |
+'</ul>' | |
+'</li></ul>' | |
+'</div>' | |
+'</div></div>', | |
gmHelp = '<div style="border: 2px solid black; background-color: white; padding: 3px 3px;">' | |
+'<div style="padding-left:10px;margin-bottom:3px;font-weight: bold;font-size: 110%">' | |
+'GM Only Features' | |
+'</div>' | |
+'<div style="padding-left:10px;margin-bottom:3px;">' | |
+'<ul><li style="border-top: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--vcontrol,volume:<i>value</i>,[ignore],[fade/mute/unmute,[tickTime:<i>seconds</i>],[volTick:<i>number</i>],[stop]],[delay:<i>seconds</i>]|track to change|track to change|...</span></b><br>' | |
+'Adjusts the volume of the indicated tracks. Has no effect on playlists. Players can be given access to volume control by turning access restriction off in the config menu.' | |
+'<ul>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">volume - </span></b>What to set the volume to. This can be a number, an expression, or mute/unmute. Valid expressions are -X,+X,/X,and *X.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">ignore - </span></b>Inverts the command, adjusting the volume of all tracks/playlists except those listed. If no track or playlist is listed, adjusts the master volume.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">fade - </span></b>Allows you to adjust the volume of the indicated track(s) gradually over time. The volume is adjusted to the value passed in volume as with a normal vcontrol command. Has three optional settings.<br>' | |
+'<div style="padding-left:10px;">' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">tickTime - </span></b>How frequently the volume should be adjusted during the fade<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">volTick - </span></b>How much to adjust the volume by each tick. Will be modified on the last tick if this change would bypass the target volume.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">stop - </span></b>Stops the track(s) once they reach the target volume.<br>' | |
+'</div>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">delay:<i>seconds</i> - </span></b>Delays the execution of the action group by this many seconds.<br>' | |
+'</ul>' | |
+'</li></div>' | |
+'<div style="padding-left:10px;margin-bottom:3px;">' | |
+'<ul><li style="border-top: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--listCreate,listName:<i>name</i>,[access:gm/player][mode:shuffle/trueRandom/together/single]|track to add|track to add|...</span></b><br> ' | |
+'Creates a new script defined playlist. <b>Note:</b> These are different from the jukebox playlists, and have no relation to those as they are not API accessible.' | |
+'<ul>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">listName - </span></b>The name of the new playlist. Must be unique among playlist <b>and</b> track names.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">mode:shuffle - </span></b>Sets the default playlist behavior to shuffle.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">mode:trueRandom - </span></b>Sets the default playlist behavior to trueRandom<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">mode:together - </span></b>Sets the default playlist behavior to together<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">mode:single - </span></b>Sets the default playlist behavior to single. Will play a random single track.<br>' | |
+'&'+'#8226'+'; If no behavior is specified, will default to single.<br>' | |
+'&'+'#8226'+'; <b><span style="font-family: serif;">access:<i>gm/player</i> - </span></b>Sets the access for this playlist to be player accessible or gm only. Default is gm only.<br>' | |
+'</ul>' | |
+'</li></ul>' | |
+'<ul><li style="border-top: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--listEdit,listName:name,[add/remove],[mode:shuffle/trueRandom/single/together],[access:gm/player]|track to add|track to add|...</span></b><br> ' | |
+'Edits the indicated playlist.' | |
+'<ul>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">add - </span></b>Adds the track(s) to the playlist<br>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">remove - </span></b>Removes the tracks from the playlist. If no tracks are specified, deletes the playlist.<br>' | |
+'</ul>' | |
+'</li></ul>' | |
+'<ul><li style="border-top: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--cancelDelay</span></b><br> ' | |
+'Cancels all delays except for the fading of volumes. There is not currently a more specific cancellation of delays.' | |
+'</li></ul>' | |
+'<ul><li style="border-top: 1px solid #ccc;border-bottom: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--config,[menu:<i>Which Menu</i>]</span></b><br> ' | |
+'Brings up the chat based menu interface for the script' | |
+'<ul>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">menu:<i>Which Menu</i> - </span></b>Specifies one of the config menus to send to chat. Possibilities are:<br>' | |
+'<ul>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">playlists - </span></b>Brings up the list of created playlists and their current play state and player access state and the controls to play/stop them, add more playlists, toggle player access and see the details of an individual playlist.<br>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">tracks - </span></b>Brings up the list of all tracks and their current volume and play state and controls to change these.<br>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">restrict - </span></b>Brings up the menu to set how the script reacts to player and API messages and what denotes a player accessible track.<br>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">playlist name - </span></b>Providing the name of a playlist will bring up the details menu for that playlist.<br>' | |
+'</ul>' | |
+'</ul>' | |
+'</li></ul>' | |
+'<ul><li style="border-bottom: 1px solid #ccc;">' | |
+'<b><span style="font-family: serif;">--listImport,[listName:name]</span></b><br> ' | |
+'Imports playlists from the jukebox into the script. If the named playlist already exists, it will update that playlist with any missing tracks.' | |
+'<ul>' | |
+'&'+'#8226'+';<b><span style="font-family: serif;">listName:name - </span></b>Will only import or update the designated playlist.<br>' | |
+'</ul>' | |
+'</li></ul>' | |
+'</div>' | |
+'</div>'; | |
help.set({notes:playerHelp}); | |
help.set({gmnotes:gmHelp}); | |
}, | |
//wipes out list or track | |
destroyHandler = function(obj){ | |
delete state.Roll20AM.trackDetails[obj.id]; | |
_.each(state.Roll20AM.playLists,(L)=>{ | |
L.trackids.splice(L.trackids.indexOf(obj.id),1); | |
}); | |
}, | |
RegisterEventHandlers = function() { | |
on('chat:message', inputHandler); | |
on('change:jukeboxtrack',changeHandler); | |
// on('add:jukeboxtrack',storeTracks); | |
on('destroy:jukeboxtrack',destroyHandler); | |
on('destroy:handout',(obj)=>{ | |
if(obj.id === state.Roll20AM.help){ | |
log('-=> Roll20AM re-initializing; Roll20AM journal entry deleted.'); | |
sendChat('Roll20AM',"/w gm It looks like you have deleted the help document for the Roll20AM script. We're regenerating it so that you can easily " | |
+"access all the information you need to utilize the script. It will be stored in your archived folder and won't clutter up your journal.",null,{noarchive:true}); | |
state.Roll20AM.help = createObj('handout',{ | |
name:'Roll20AM', | |
inplayerjournals:'all', | |
archived:true | |
}).id; | |
generateHelp(); | |
} | |
}); | |
}; | |
return { | |
CheckInstall: checkInstall, | |
RegisterEventHandlers: RegisterEventHandlers | |
} | |
}()); | |
on("ready",function(){ | |
'use strict'; | |
Roll20AM.CheckInstall(); | |
Roll20AM.RegisterEventHandlers(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment