/* * Version 0.1.13 * Made By Robin Kuiper * Skype: RobinKuiper.eu * Discord: Atheos#1095 * Roll20: https://app.roll20.net/users/1226016/robin * Roll20 Wiki: https://wiki.roll20.net/Script:Concentration * Roll20 Thread: https://app.roll20.net/forum/post/6364317/script-concentration/?pageforid=6364317#post-6364317 * Github: https://github.com/RobinKuiper/Roll20APIScripts * Reddit: https://www.reddit.com/user/robinkuiper/ * Patreon: https://patreon.com/robinkuiper * Paypal.me: https://www.paypal.me/robinkuiper */ var Concentration = Concentration || (function() { 'use strict'; let checked = []; // Styling for the chat responses. const styles = { reset: 'padding: 0; margin: 0;', menu: 'background-color: #fff; border: 1px solid #000; padding: 5px; border-radius: 5px;', button: 'background-color: #000; border: 1px solid #292929; border-radius: 3px; padding: 5px; color: #fff; text-align: center;', list: 'list-style: none;', float: { right: 'float: right;', left: 'float: left;' }, overflow: 'overflow: hidden;', fullWidth: 'width: 100%;' }, script_name = 'Concentration', state_name = 'CONCENTRATION', markers = ['blue', 'brown', 'green', 'pink', 'purple', 'red', 'yellow', '-', 'all-for-one', 'angel-outfit', 'archery-target', 'arrowed', 'aura', 'back-pain', 'black-flag', 'bleeding-eye', 'bolt-shield', 'broken-heart', 'broken-shield', 'broken-skull', 'chained-heart', 'chemical-bolt', 'cobweb', 'dead', 'death-zone', 'drink-me', 'edge-crack', 'fishing-net', 'fist', 'fluffy-wing', 'flying-flag', 'frozen-orb', 'grab', 'grenade', 'half-haze', 'half-heart', 'interdiction', 'lightning-helix', 'ninja-mask', 'overdrive', 'padlock', 'pummeled', 'radioactive', 'rolling-tomb', 'screaming', 'sentry-gun', 'skull', 'sleepy', 'snail', 'spanner', 'stopwatch','strong', 'three-leaves', 'tread', 'trophy', 'white-tower'], handleInput = (msg) => { if(state[state_name].config.auto_add_concentration_marker && msg && msg.rolltemplate && msg.rolltemplate === 'spell' && (msg.content.includes("{{concentration=1}}"))){ handleConcentrationSpellCast(msg); } if (msg.type != 'api') return; // Split the message into command and argument(s) let args = msg.content.split(' '); let command = args.shift().substring(1); let extracommand = args.shift(); let message; if (command == state[state_name].config.command) { if(playerIsGM(msg.playerid)){ switch(extracommand){ case 'reset': state[state_name] = {}; setDefaults(true); sendConfigMenu(false, 'The API Library needs to be restarted for this to take effect.'); break; case 'config': if(args.length > 0){ let setting = args.shift().split('|'); let key = setting.shift(); let value = (setting[0] === 'true') ? true : (setting[0] === 'false') ? false : setting[0]; state[state_name].config[key] = value; if(key === 'bar'){ //registerEventHandlers(); message = 'The API Library needs to be restarted for this to take effect.'; } } sendConfigMenu(false, message); break; case 'roll': let DC = parseInt(args[0], 10), con_save_mod = parseInt(args[1], 10), name = args[2], target = args[3]; roll(DC, con_save_mod, name, target, false); break; case 'advantage': let DCa = parseInt(args[0], 10), con_save_moda = parseInt(args[1], 10), namea = args[2], targeta = args[3]; roll(DCa, con_save_moda, namea, targeta, true); break; default: if(msg.selected && msg.selected.length){ msg.selected.forEach(s => { let token = getObj(s._type, s._id); addConcentration(token, msg.playerid, extracommand); }); return; } sendConfigMenu(); break; } }else{ if(msg.selected && msg.selected.length){ msg.selected.forEach(s => { let token = getObj(s._type, s._id); addConcentration(token, msg.playerid, extracommand); }); } } } }, addConcentration = (token, playerid, spell) => { const marker = state[state_name].config.statusmarker let character = getObj('character', token.get('represents')); if((token.get('controlledby').split(',').includes(playerid) || token.get('controlledby').split(',').includes('all')) || (character && (character.get('controlledby').split(',').includes(playerid) || character.get('controlledby').split(',').includes('all'))) || playerIsGM(playerid)){ if(!token.get('status_'+marker)){ let target = state[state_name].config.send_reminder_to; if(target === 'character'){ target = createWhisperName(character_name); }else if(target === 'everyone'){ target = '' } let message; if(spell){ message = ''+token.get('name')+' is now concentrating on '+spell+'.'; }else{ message = ''+token.get('name')+' is now concentrating.'; } makeAndSendMenu(message, '', target); } token.set('status_'+marker, !token.get('status_'+marker)); } }, handleConcentrationSpellCast = (msg) => { const marker = state[state_name].config.statusmarker let character_name = msg.content.match(/charname=([^\n{}]*[^"\n{}])/); character_name = RegExp.$1; let spell_name = msg.content.match(/name=([^\n{}]*[^"\n{}])/); spell_name = RegExp.$1; let player = getObj('player', msg.playerid), characterid = findObjs({ name: character_name, _type: 'character' }).shift().get('id'), represented_tokens = findObjs({ represents: characterid, _type: 'graphic' }), message, target = state[state_name].config.send_reminder_to; if(!character_name || !spell_name || !player || !characterid) return; let search_attributes = { represents: characterid, _type: 'graphic', _pageid: player.get('lastpage') } search_attributes['status_'+marker] = true; let is_concentrating = (findObjs(search_attributes).length > 0); if(is_concentrating){ message = ''+character_name+' is concentrating already.'; }else{ represented_tokens.forEach(token => { let attributes = {}; attributes['status_'+marker] = true; token.set(attributes); message = ''+character_name+' is now concentrating on '+spell_name+'.'; }); } if(target === 'character'){ target = createWhisperName(character_name); }else if(target === 'everyone'){ target = '' } makeAndSendMenu(message, '', target); }, handleStatusMarkerChange = (obj, prev) => { const marker = state[state_name].config.statusmarker if(!obj.get('status_'+marker)){ removeMarker(obj.get('represents')); } }, handleGraphicChange = (obj, prev) => { if(checked.includes(obj.get('represents'))){ return false; } let bar = 'bar'+state[state_name].config.bar+'_value', target = state[state_name].config.send_reminder_to, marker = state[state_name].config.statusmarker; if(prev && obj.get('status_'+marker) && obj.get(bar) < prev[bar]){ let calc_DC = Math.floor((prev[bar] - obj.get(bar))/2), DC = (calc_DC > 10) ? calc_DC : 10, con_save_mod = parseInt(getAttrByName(obj.get('represents'), state[state_name].config.bonus_attribute, 'current')) || 0, chat_text; if(target === 'character'){ chat_text = "Make a Concentration Check - DC " + DC + "."; target = createWhisperName(obj.get('name')); }else if(target === 'everyone'){ chat_text = ''+obj.get('name')+' must make a Concentration Check - DC ' + DC + '.'; target = ''; }else{ chat_text = ''+obj.get('name')+' must make a Concentration Check - DC ' + DC + '.'; target = 'gm'; } if(state[state_name].config.show_roll_button){ chat_text += '
| DC | \'+DC+' | \
|---|---|
| Modifier | \'+con_save_mod+' | \
| Roll Result | \'+rollResultString+' | \
Works only for 5e OGL Sheet.
' + addConMarkerButton, 'Auto Roll Save: ' + autoRollButton, ], resetButton = makeButton('Reset', '!' + state[state_name].config.command + ' reset', styles.button + styles.fullWidth), title_text = (first) ? script_name + ' First Time Setup' : script_name + ' Config'; if(state[state_name].config.auto_roll_save){ listItems.push('Advantage: ' + advantageButton); } if(state[state_name].config.auto_roll_save){ listItems.push('Bonus Attribute: ' + bonusAttrButton) } if(!state[state_name].config.auto_roll_save){ listItems.push('Roll Button: ' + showRollButtonButton); } message = (message) ? ''+message+'
' : ''; let contents = message+makeList(listItems, styles.reset + styles.list + styles.overflow, styles.overflow)+'You can always come back to this config by typing `!'+state[state_name].config.command+' config`.