diff --git a/Concentration/0.1.14/Concentration.js b/Concentration/0.1.14/Concentration.js index 799baf4f1e..dc2eada5e6 100644 --- a/Concentration/0.1.14/Concentration.js +++ b/Concentration/0.1.14/Concentration.js @@ -79,36 +79,39 @@ var Concentration = Concentration || (function() { sendAdvantageMenu(); break; - case 'toggle-advantage': - let id = args[0]; + case 'toggle-advantage': { + let id = args[0]; - if(state[state_name].advantages[id]){ - state[state_name].advantages[id] = !state[state_name].advantages[id]; - }else{ - state[state_name].advantages[id] = true; - } + if(state[state_name].advantages[id]){ + state[state_name].advantages[id] = !state[state_name].advantages[id]; + }else{ + state[state_name].advantages[id] = true; + } - sendAdvantageMenu(); + sendAdvantageMenu(); + } break; - case 'roll': - let represents = args[0], - DC = parseInt(args[1], 10), - con_save_mod = parseInt(args[2], 10), - name = args[3], - target = args[4]; + case 'roll': { + let represents = args[0], + DC = parseInt(args[1], 10), + con_save_mod = parseInt(args[2], 10), + name = args[3], + target = args[4]; - roll(represents, DC, con_save_mod, name, target, false); + roll(represents, DC, con_save_mod, name, target, false); + } break; - case 'advantage': - let represents_a = args[0], + case 'advantage': { + let represents_a = args[0], DC_a = parseInt(args[1], 10), con_save_mod_a = parseInt(args[2], 10), name_a = args[3], target_a = args[4]; roll(represents_a, DC_a, con_save_mod_a, name_a, target_a, true); + } break; default: @@ -135,7 +138,7 @@ var Concentration = Concentration || (function() { }, addConcentration = (token, playerid, spell) => { - const marker = state[state_name].config.statusmarker + 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'))) || @@ -143,9 +146,9 @@ var Concentration = Concentration || (function() { if(!token.get('status_'+marker)){ let target = state[state_name].config.send_reminder_to; if(target === 'character'){ - target = createWhisperName(character_name); + target = character.get('name'); }else if(target === 'everyone'){ - target = '' + target = ''; } let message; @@ -162,7 +165,7 @@ var Concentration = Concentration || (function() { }, handleConcentrationSpellCast = (msg) => { - const marker = state[state_name].config.statusmarker + const marker = state[state_name].config.statusmarker; let character_name = msg.content.match(/charname=([^\n{}]*[^"\n{}])/); character_name = RegExp.$1; @@ -180,7 +183,7 @@ var Concentration = Concentration || (function() { represents: characterid, _type: 'graphic', _pageid: player.get('lastpage') - } + }; search_attributes['status_'+marker] = true; let is_concentrating = (findObjs(search_attributes).length > 0); @@ -196,16 +199,16 @@ var Concentration = Concentration || (function() { } if(target === 'character'){ - target = createWhisperName(character_name); + target = character_name; }else if(target === 'everyone'){ - target = '' + target = ''; } makeAndSendMenu(message, '', target); }, - handleStatusMarkerChange = (obj, prev) => { - const marker = state[state_name].config.statusmarker + handleStatusMarkerChange = (obj /*, prev */) => { + const marker = state[state_name].config.statusmarker; if(!obj.get('status_'+marker)){ removeMarker(obj.get('represents')); @@ -227,7 +230,7 @@ var Concentration = Concentration || (function() { if(target === 'character'){ chat_text = "Make a Concentration Check - DC " + DC + "."; - target = createWhisperName(obj.get('name')); + target = obj.get('name'); }else if(target === 'everyone'){ chat_text = ''+obj.get('name')+' must make a Concentration Check - DC ' + DC + '.'; target = ''; @@ -256,11 +259,15 @@ var Concentration = Concentration || (function() { }, roll = (represents, DC, con_save_mod, name, target, advantage) => { - sendChat(script_name, '[[1d20cf<'+(DC-con_save_mod-1)+'cs>'+(DC-con_save_mod-1)+'+'+con_save_mod+']]', results => { + // Bound the crit success and fail targets so negatives stop wrecking the roll --Oosh + const criticalFail = Math.max(DC-con_save_mod-1, 0), + criticalSuccess = Math.max(DC-con_save_mod, 0), + rollString = `[[1d20cf<${criticalFail}cs>${criticalSuccess} + (${con_save_mod})]]`; + sendChat(script_name, rollString, results => { let title = 'Concentration Save
'+name+'', advantageRollResult; - - let rollresult = results[0].inlinerolls[0].results.rolls[0].results[0].v; + // Error check the results object and debug for any future issues --Oosh + let rollresult = results ? results[0].inlinerolls[0].results.rolls[0].results[0].v : `Roll error! DC: "${DC}", con_sav_mod: "${con_save_mod}"`; let result = rollresult; if(advantage){ @@ -297,7 +304,7 @@ var Concentration = Concentration || (function() { [['+result+'+'+con_save_mod+']]

\ '+result_text+' \ \ - ' + '; makeAndSendMenu(contents, title, target); if(target !== '' && target !== 'gm'){ @@ -316,10 +323,6 @@ var Concentration = Concentration || (function() { }); }, - createWhisperName = (name) => { - return name.split(' ').shift(); - }, - ucFirst = (string) => { return string.charAt(0).toUpperCase() + string.slice(1); }, @@ -327,8 +330,8 @@ var Concentration = Concentration || (function() { sendConfigMenu = (first, message) => { let markerDropdown = '?{Marker'; markers.forEach((marker) => { - markerDropdown += '|'+ucFirst(marker).replace('-', ' ')+','+marker - }) + markerDropdown += '|'+ucFirst(marker).replace('-', ' ')+','+marker; + }); markerDropdown += '}'; let markerButton = makeButton(state[state_name].config.statusmarker, '!' + state[state_name].config.command + ' config statusmarker|'+markerDropdown, styles.button + styles.float.right), @@ -347,7 +350,7 @@ var Concentration = Concentration || (function() { 'HP Bar: ' + barButton, 'Send Reminder To: ' + sendToButton, 'Auto Add Con. Marker:

Works only for 5e OGL Sheet.

' + addConMarkerButton, - 'Auto Roll Save: ' + autoRollButton, + 'Auto Roll Save: ' + autoRollButton ], resetButton = makeButton('Reset', '!' + state[state_name].config.command + ' reset', styles.button + styles.fullWidth), @@ -359,7 +362,7 @@ var Concentration = Concentration || (function() { }*/ if(state[state_name].config.auto_roll_save){ - listItems.push('Bonus Attribute: ' + bonusAttrButton) + listItems.push('Bonus Attribute: ' + bonusAttrButton); } if(!state[state_name].config.auto_roll_save){ @@ -393,9 +396,9 @@ var Concentration = Concentration || (function() { makeAndSendMenu(menu_text, 'Advantage Menu', 'gm'); }, - makeAndSendMenu = (contents, title, whisper, callback) => { + makeAndSendMenu = (contents, title, whisper /*, callback */) => { title = (title && title != '') ? makeTitle(title) : ''; - whisper = (whisper && whisper !== '') ? '/w ' + whisper + ' ' : ''; + whisper = (whisper && whisper !== '') ? `/w "${whisper}" ` : ''; sendChat(script_name, whisper + '
'+title+contents+'
', null, {noarchive:true}); }, @@ -416,12 +419,14 @@ var Concentration = Concentration || (function() { return list; }, +/* pre_log = (message) => { log('---------------------------------------------------------------------------------------------'); if(!message){ return; } log(message); log('---------------------------------------------------------------------------------------------'); }, + */ checkInstall = () => { if(!_.has(state, state_name)){ @@ -430,13 +435,15 @@ var Concentration = Concentration || (function() { setDefaults(); log(script_name + ' Ready! Command: !'+state[state_name].config.command); - if(state[state_name].config.debug){ makeAndSendMenu(script_name + ' Ready! Debug On.', '', 'gm') } + if(state[state_name].config.debug){ makeAndSendMenu(script_name + ' Ready! Debug On.', '', 'gm'); } }, registerEventHandlers = () => { on('chat:message', handleInput); on('change:graphic:bar'+state[state_name].config.bar+'_value', handleGraphicChange); on('change:graphic:statusmarkers', handleStatusMarkerChange); + // Add tokenMod observer so changes made with TM will trigger a conc. check --Oosh + if (typeof(TokenMod) === 'object') TokenMod.ObserveTokenChange(handleGraphicChange); }, setDefaults = (reset) => { @@ -499,7 +506,7 @@ var Concentration = Concentration || (function() { return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers - } + }; })(); on('ready',function() { @@ -507,4 +514,4 @@ on('ready',function() { Concentration.CheckInstall(); Concentration.RegisterEventHandlers(); -}); \ No newline at end of file +}); diff --git a/Concentration/0.2.0/Concentration.js b/Concentration/0.2.0/Concentration.js new file mode 100644 index 0000000000..ea74a809dc --- /dev/null +++ b/Concentration/0.2.0/Concentration.js @@ -0,0 +1,522 @@ +/* + * Version 0.2.0 + * Made By Robin Kuiper + * Skype: RobinKuiper.eu + * Discord: Atheos#1095 + * My Discord Server: https://discord.gg/AcC9VME + * 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;', + textButton: 'background-color: transparent; border: none; padding: 0; color: #000; text-decoration: underline', + 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(state[state_name].config.auto_add_concentration_marker && msg && msg.type && msg.type === 'advancedroll' && /data\-chip=concentration/g.test(msg.content)){ + handleConcentrationSpellCast(msg, true); + } + + 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 'advantage-menu': + sendAdvantageMenu(); + break; + + case 'toggle-advantage': + let id = args[0]; + + if(state[state_name].advantages[id]){ + state[state_name].advantages[id] = !state[state_name].advantages[id]; + }else{ + state[state_name].advantages[id] = true; + } + + sendAdvantageMenu(); + break; + + case 'roll': + let represents = args[0], + DC = parseInt(args[1], 10), + con_save_mod = parseInt(args[2], 10), + name = args[3], + target = args[4]; + + roll(represents, DC, con_save_mod, name, target, false); + break; + + case 'advantage': + let represents_a = args[0], + DC_a = parseInt(args[1], 10), + con_save_mod_a = parseInt(args[2], 10), + name_a = args[3], + target_a = args[4]; + + roll(represents_a, DC_a, con_save_mod_a, name_a, target_a, 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, is2024=false) => { + const marker = state[state_name].config.statusmarker + + let character_name, spell_name; + + if(is2024) { + character_name = msg.content.match(/meta__character.+?>(.*?)(.*?) 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 = async (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(await getSheetItem(obj.get('represents'), state[state_name].config.bonus_attribute)) || 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 += '
' + makeButton('Advantage', '!' + state[state_name].config.command + ' advantage ' + obj.get('represents') + ' ' + DC + ' ' + con_save_mod + ' ' + obj.get('name') + ' ' + target, styles.button + styles.float.right); + chat_text += ' ' + makeButton('Roll', '!' + state[state_name].config.command + ' roll ' + obj.get('represents') + ' ' + DC + ' ' + con_save_mod + ' ' + obj.get('name') + ' ' + target, styles.button + styles.float.left); + } + + if(state[state_name].config.auto_roll_save){ + //&{template:default} {{name='+obj.get('name')+' - Concentration Save}} {{Modifier='+con_save_mod+'}} {{Roll=[[1d20cf<'+(DC-con_save_mod-1)+'cs>'+(DC-con_save_mod-1)+'+'+con_save_mod+']]}} {{DC='+DC+'}} + roll(obj.get('represents'), DC, con_save_mod, obj.get('name'), target, state[state_name].advantages[obj.get('represents')]); + }else{ + makeAndSendMenu(chat_text, '', target); + } + + let length = checked.push(obj.get('represents')); + setTimeout(() => { + checked.splice(length-1, 1); + }, 1000); + } + }, + + roll = (represents, DC, con_save_mod, name, target, advantage) => { + sendChat(script_name, '[[1d20cf<'+(DC-con_save_mod-1)+'cs>'+(DC-con_save_mod-1)+'+'+con_save_mod+']]', results => { + let title = 'Concentration Save
'+name+'', + advantageRollResult; + + let rollresult = results[0].inlinerolls[0].results.rolls[0].results[0].v; + let result = rollresult; + + if(advantage){ + advantageRollResult = randomInteger(20); + result = (rollresult <= advantageRollResult) ? advantageRollResult : rollresult; + } + + let total = result + con_save_mod; + + let success = total >= DC; + + let result_text = (success) ? 'Success' : 'Failed', + result_color = (success) ? 'green' : 'red'; + + let rollResultString = (advantage) ? rollresult + ' / ' + advantageRollResult : rollresult; + + let contents = ' \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +
DC'+DC+'
Modifier'+con_save_mod+'
Roll Result'+rollResultString+'
\ +
\ + \ + [['+result+'+'+con_save_mod+']]

\ + '+result_text+' \ +
\ +
' + makeAndSendMenu(contents, title, target); + + if(target !== '' && target !== 'gm'){ + makeAndSendMenu(contents, title, 'gm'); + } + + if(!success){ + removeMarker(represents); + } + }); + }, + + removeMarker = (represents, type='graphic') => { + findObjs({ type, represents }).forEach(o => { + o.set('status_'+state[state_name].config.statusmarker, false); + }); + }, + + createWhisperName = (name) => { + return name.split(' ').shift(); + }, + + ucFirst = (string) => { + return string.charAt(0).toUpperCase() + string.slice(1); + }, + + sendConfigMenu = (first, message) => { + let markerDropdown = '?{Marker'; + markers.forEach((marker) => { + markerDropdown += '|'+ucFirst(marker).replace('-', ' ')+','+marker + }) + markerDropdown += '}'; + + let markerButton = makeButton(state[state_name].config.statusmarker, '!' + state[state_name].config.command + ' config statusmarker|'+markerDropdown, styles.button + styles.float.right), + commandButton = makeButton('!'+state[state_name].config.command, '!' + state[state_name].config.command + ' config command|?{Command (without !)}', styles.button + styles.float.right), + barButton = makeButton('bar ' + state[state_name].config.bar, '!' + state[state_name].config.command + ' config bar|?{Bar|Bar 1 (green),1|Bar 2 (blue),2|Bar 3 (red),3}', styles.button + styles.float.right), + sendToButton = makeButton(state[state_name].config.send_reminder_to, '!' + state[state_name].config.command + ' config send_reminder_to|?{Send To|Everyone,everyone|Character,character|GM,gm}', styles.button + styles.float.right), + addConMarkerButton = makeButton(state[state_name].config.auto_add_concentration_marker, '!' + state[state_name].config.command + ' config auto_add_concentration_marker|'+!state[state_name].config.auto_add_concentration_marker, styles.button + styles.float.right), + autoRollButton = makeButton(state[state_name].config.auto_roll_save, '!' + state[state_name].config.command + ' config auto_roll_save|'+!state[state_name].config.auto_roll_save, styles.button + styles.float.right), + //advantageButton = makeButton(state[state_name].config.advantage, '!' + state[state_name].config.command + ' config advantage|'+!state[state_name].config.advantage, styles.button + styles.float.right), + bonusAttrButton = makeButton(state[state_name].config.bonus_attribute, '!' + state[state_name].config.command + ' config bonus_attribute|?{Attribute|'+state[state_name].config.bonus_attribute+'}', styles.button + styles.float.right), + showRollButtonButton = makeButton(state[state_name].config.show_roll_button, '!' + state[state_name].config.command + ' config show_roll_button|'+!state[state_name].config.show_roll_button, styles.button + styles.float.right), + + listItems = [ + 'Command: ' + commandButton, + 'Statusmarker: ' + markerButton, + 'HP Bar: ' + barButton, + 'Send Reminder To: ' + sendToButton, + 'Auto Add Con. Marker:

Works only for 5e OGL and 2024 Sheets.

' + 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); + } + + let advantageMenuButton = (state[state_name].config.auto_roll_save) ? makeButton('Advantage Menu', '!' + state[state_name].config.command + ' advantage-menu', styles.button + styles.fullWidth) : ''; + + message = (message) ? '

'+message+'

' : ''; + let contents = message+makeList(listItems, styles.reset + styles.list + styles.overflow, styles.overflow)+'
'+advantageMenuButton+'

You can always come back to this config by typing `!'+state[state_name].config.command+' config`.


'+resetButton; + makeAndSendMenu(contents, title_text, 'gm'); + }, + + sendAdvantageMenu = () => { + let menu_text = ""; + let characters = findObjs({ type: 'character' }).sort((a, b) => { + let nameA = a.get('name').toUpperCase(); + let nameB = b.get('name').toUpperCase(); + + if(nameA < nameB) return -1; + if(nameA > nameB) return 1; + + return 0; + }); + + characters.forEach(character => { + let name = (state[state_name].advantages && state[state_name].advantages[character.get('id')]) ? ''+character.get('name')+'' : character.get('name'); + menu_text += makeButton(name, '!' + state[state_name].config.command + ' toggle-advantage ' + character.get('id'), styles.textButton) + '
'; + }); + + makeAndSendMenu(menu_text, 'Advantage Menu', 'gm'); + }, + + makeAndSendMenu = (contents, title, whisper, callback) => { + title = (title && title != '') ? makeTitle(title) : ''; + whisper = (whisper && whisper !== '') ? '/w ' + whisper + ' ' : ''; + sendChat(script_name, whisper + '
'+title+contents+'
', null, {noarchive:true}); + }, + + makeTitle = (title) => { + return '

'+title+'

'; + }, + + makeButton = (title, href, style) => { + return ''+title+''; + }, + + makeList = (items, listStyle, itemStyle) => { + let list = ''; + return list; + }, + + pre_log = (message) => { + log('---------------------------------------------------------------------------------------------'); + if(!message){ return; } + log(message); + log('---------------------------------------------------------------------------------------------'); + }, + + checkInstall = () => { + if(!_.has(state, state_name)){ + state[state_name] = state[state_name] || {}; + } + setDefaults(); + + log(script_name + ' Ready! Command: !'+state[state_name].config.command); + if(state[state_name].config.debug){ makeAndSendMenu(script_name + ' Ready! Debug On.', '', 'gm') } + }, + + registerEventHandlers = () => { + on('chat:message', handleInput); + on('change:graphic:bar'+state[state_name].config.bar+'_value', handleGraphicChange); + on('change:graphic:statusmarkers', handleStatusMarkerChange); + }, + + setDefaults = (reset) => { + const defaults = { + config: { + command: 'concentration', + statusmarker: 'stopwatch', + bar: 1, + send_reminder_to: 'everyone', // character,gm, + auto_add_concentration_marker: true, + auto_roll_save: true, + advantage: false, + bonus_attribute: 'constitution_save_mod', + show_roll_button: true + }, + advantages: {} + }; + + if(!state[state_name].config){ + state[state_name].config = defaults.config; + }else{ + if(!state[state_name].config.hasOwnProperty('command')){ + state[state_name].config.command = defaults.config.command; + } + if(!state[state_name].config.hasOwnProperty('statusmarker')){ + state[state_name].config.statusmarker = defaults.config.statusmarker; + } + if(!state[state_name].config.hasOwnProperty('bar')){ + state[state_name].config.bar = defaults.config.bar; + } + if(!state[state_name].config.hasOwnProperty('send_reminder_to')){ + state[state_name].config.send_reminder_to = defaults.config.send_reminder_to; + } + if(!state[state_name].config.hasOwnProperty('auto_add_concentration_marker')){ + state[state_name].config.auto_add_concentration_marker = defaults.config.auto_add_concentration_marker; + } + if(!state[state_name].config.hasOwnProperty('auto_roll_save')){ + state[state_name].config.auto_roll_save = defaults.config.auto_roll_save; + } + if(!state[state_name].config.hasOwnProperty('advantage')){ + state[state_name].config.advantage = defaults.config.advantage; + } + if(!state[state_name].config.hasOwnProperty('bonus_attribute')){ + state[state_name].config.bonus_attribute = defaults.config.bonus_attribute; + } + if(!state[state_name].config.hasOwnProperty('show_roll_button')){ + state[state_name].config.show_roll_button = defaults.config.show_roll_button; + } + } + if(!state[state_name].advantages){ + state[state_name].advantages = defaults.advantages; + } + + if(!state[state_name].config.hasOwnProperty('firsttime') && !reset){ + sendConfigMenu(true); + state[state_name].config.firsttime = false; + } + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: registerEventHandlers + } +})(); + +on('ready',function() { + 'use strict'; + + Concentration.CheckInstall(); + Concentration.RegisterEventHandlers(); +}); \ No newline at end of file diff --git a/Concentration/Concentration.js b/Concentration/Concentration.js index dc2eada5e6..0e03f6c4ec 100644 --- a/Concentration/Concentration.js +++ b/Concentration/Concentration.js @@ -1,5 +1,5 @@ /* - * Version 0.1.14 + * Version 0.1.15 * Made By Robin Kuiper * Skype: RobinKuiper.eu * Discord: Atheos#1095 @@ -41,6 +41,10 @@ var Concentration = Concentration || (function() { handleConcentrationSpellCast(msg); } + if(state[state_name].config.auto_add_concentration_marker && msg && msg.type && msg.type === 'advancedroll' && /data\-chip=concentration/g.test(msg.content)){ + handleConcentrationSpellCast(msg, true); + } + if (msg.type != 'api') return; // Split the message into command and argument(s) @@ -79,39 +83,36 @@ var Concentration = Concentration || (function() { sendAdvantageMenu(); break; - case 'toggle-advantage': { - let id = args[0]; + case 'toggle-advantage': + let id = args[0]; - if(state[state_name].advantages[id]){ - state[state_name].advantages[id] = !state[state_name].advantages[id]; - }else{ - state[state_name].advantages[id] = true; - } - - sendAdvantageMenu(); + if(state[state_name].advantages[id]){ + state[state_name].advantages[id] = !state[state_name].advantages[id]; + }else{ + state[state_name].advantages[id] = true; } + + sendAdvantageMenu(); break; - case 'roll': { - let represents = args[0], - DC = parseInt(args[1], 10), - con_save_mod = parseInt(args[2], 10), - name = args[3], - target = args[4]; + case 'roll': + let represents = args[0], + DC = parseInt(args[1], 10), + con_save_mod = parseInt(args[2], 10), + name = args[3], + target = args[4]; - roll(represents, DC, con_save_mod, name, target, false); - } + roll(represents, DC, con_save_mod, name, target, false); break; - case 'advantage': { - let represents_a = args[0], + case 'advantage': + let represents_a = args[0], DC_a = parseInt(args[1], 10), con_save_mod_a = parseInt(args[2], 10), name_a = args[3], target_a = args[4]; roll(represents_a, DC_a, con_save_mod_a, name_a, target_a, true); - } break; default: @@ -138,7 +139,7 @@ var Concentration = Concentration || (function() { }, addConcentration = (token, playerid, spell) => { - const marker = state[state_name].config.statusmarker; + 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'))) || @@ -146,9 +147,9 @@ var Concentration = Concentration || (function() { if(!token.get('status_'+marker)){ let target = state[state_name].config.send_reminder_to; if(target === 'character'){ - target = character.get('name'); + target = createWhisperName(character_name); }else if(target === 'everyone'){ - target = ''; + target = '' } let message; @@ -164,13 +165,21 @@ var Concentration = Concentration || (function() { } }, - handleConcentrationSpellCast = (msg) => { - const marker = state[state_name].config.statusmarker; + handleConcentrationSpellCast = (msg, is2024=false) => { + const marker = state[state_name].config.statusmarker + + let character_name, spell_name; + + if(is2024) { + character_name = msg.content.match(/meta__character.+?>(.*?)(.*?) 0); @@ -199,23 +208,23 @@ var Concentration = Concentration || (function() { } if(target === 'character'){ - target = character_name; + target = createWhisperName(character_name); }else if(target === 'everyone'){ - target = ''; + target = '' } makeAndSendMenu(message, '', target); }, - handleStatusMarkerChange = (obj /*, prev */) => { - const marker = state[state_name].config.statusmarker; + handleStatusMarkerChange = (obj, prev) => { + const marker = state[state_name].config.statusmarker if(!obj.get('status_'+marker)){ removeMarker(obj.get('represents')); } }, - handleGraphicChange = (obj, prev) => { + handleGraphicChange = async (obj, prev) => { if(checked.includes(obj.get('represents'))){ return false; } let bar = 'bar'+state[state_name].config.bar+'_value', @@ -225,12 +234,12 @@ var Concentration = Concentration || (function() { 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, + con_save_mod = parseInt(await getSheetItem(obj.get('represents'), state[state_name].config.bonus_attribute)) || 0, chat_text; if(target === 'character'){ chat_text = "Make a Concentration Check - DC " + DC + "."; - target = obj.get('name'); + target = createWhisperName(obj.get('name')); }else if(target === 'everyone'){ chat_text = ''+obj.get('name')+' must make a Concentration Check - DC ' + DC + '.'; target = ''; @@ -259,15 +268,11 @@ var Concentration = Concentration || (function() { }, roll = (represents, DC, con_save_mod, name, target, advantage) => { - // Bound the crit success and fail targets so negatives stop wrecking the roll --Oosh - const criticalFail = Math.max(DC-con_save_mod-1, 0), - criticalSuccess = Math.max(DC-con_save_mod, 0), - rollString = `[[1d20cf<${criticalFail}cs>${criticalSuccess} + (${con_save_mod})]]`; - sendChat(script_name, rollString, results => { + sendChat(script_name, '[[1d20cf<'+(DC-con_save_mod-1)+'cs>'+(DC-con_save_mod-1)+'+'+con_save_mod+']]', results => { let title = 'Concentration Save
'+name+'', advantageRollResult; - // Error check the results object and debug for any future issues --Oosh - let rollresult = results ? results[0].inlinerolls[0].results.rolls[0].results[0].v : `Roll error! DC: "${DC}", con_sav_mod: "${con_save_mod}"`; + + let rollresult = results[0].inlinerolls[0].results.rolls[0].results[0].v; let result = rollresult; if(advantage){ @@ -304,7 +309,7 @@ var Concentration = Concentration || (function() { [['+result+'+'+con_save_mod+']]

\ '+result_text+' \ \ - '; + ' makeAndSendMenu(contents, title, target); if(target !== '' && target !== 'gm'){ @@ -323,6 +328,10 @@ var Concentration = Concentration || (function() { }); }, + createWhisperName = (name) => { + return name.split(' ').shift(); + }, + ucFirst = (string) => { return string.charAt(0).toUpperCase() + string.slice(1); }, @@ -330,8 +339,8 @@ var Concentration = Concentration || (function() { sendConfigMenu = (first, message) => { let markerDropdown = '?{Marker'; markers.forEach((marker) => { - markerDropdown += '|'+ucFirst(marker).replace('-', ' ')+','+marker; - }); + markerDropdown += '|'+ucFirst(marker).replace('-', ' ')+','+marker + }) markerDropdown += '}'; let markerButton = makeButton(state[state_name].config.statusmarker, '!' + state[state_name].config.command + ' config statusmarker|'+markerDropdown, styles.button + styles.float.right), @@ -349,8 +358,8 @@ var Concentration = Concentration || (function() { 'Statusmarker: ' + markerButton, 'HP Bar: ' + barButton, 'Send Reminder To: ' + sendToButton, - 'Auto Add Con. Marker:

Works only for 5e OGL Sheet.

' + addConMarkerButton, - 'Auto Roll Save: ' + autoRollButton + 'Auto Add Con. Marker:

Works only for 5e OGL and 2024 Sheets.

' + addConMarkerButton, + 'Auto Roll Save: ' + autoRollButton, ], resetButton = makeButton('Reset', '!' + state[state_name].config.command + ' reset', styles.button + styles.fullWidth), @@ -362,7 +371,7 @@ var Concentration = Concentration || (function() { }*/ if(state[state_name].config.auto_roll_save){ - listItems.push('Bonus Attribute: ' + bonusAttrButton); + listItems.push('Bonus Attribute: ' + bonusAttrButton) } if(!state[state_name].config.auto_roll_save){ @@ -396,9 +405,9 @@ var Concentration = Concentration || (function() { makeAndSendMenu(menu_text, 'Advantage Menu', 'gm'); }, - makeAndSendMenu = (contents, title, whisper /*, callback */) => { + makeAndSendMenu = (contents, title, whisper, callback) => { title = (title && title != '') ? makeTitle(title) : ''; - whisper = (whisper && whisper !== '') ? `/w "${whisper}" ` : ''; + whisper = (whisper && whisper !== '') ? '/w ' + whisper + ' ' : ''; sendChat(script_name, whisper + '
'+title+contents+'
', null, {noarchive:true}); }, @@ -419,14 +428,12 @@ var Concentration = Concentration || (function() { return list; }, -/* pre_log = (message) => { log('---------------------------------------------------------------------------------------------'); if(!message){ return; } log(message); log('---------------------------------------------------------------------------------------------'); }, - */ checkInstall = () => { if(!_.has(state, state_name)){ @@ -435,15 +442,13 @@ var Concentration = Concentration || (function() { setDefaults(); log(script_name + ' Ready! Command: !'+state[state_name].config.command); - if(state[state_name].config.debug){ makeAndSendMenu(script_name + ' Ready! Debug On.', '', 'gm'); } + if(state[state_name].config.debug){ makeAndSendMenu(script_name + ' Ready! Debug On.', '', 'gm') } }, registerEventHandlers = () => { on('chat:message', handleInput); on('change:graphic:bar'+state[state_name].config.bar+'_value', handleGraphicChange); on('change:graphic:statusmarkers', handleStatusMarkerChange); - // Add tokenMod observer so changes made with TM will trigger a conc. check --Oosh - if (typeof(TokenMod) === 'object') TokenMod.ObserveTokenChange(handleGraphicChange); }, setDefaults = (reset) => { @@ -456,7 +461,7 @@ var Concentration = Concentration || (function() { auto_add_concentration_marker: true, auto_roll_save: true, advantage: false, - bonus_attribute: 'constitution_save_bonus', + bonus_attribute: 'constitution_save_mod', show_roll_button: true }, advantages: {} @@ -506,7 +511,7 @@ var Concentration = Concentration || (function() { return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers - }; + } })(); on('ready',function() { @@ -514,4 +519,4 @@ on('ready',function() { Concentration.CheckInstall(); Concentration.RegisterEventHandlers(); -}); +}); \ No newline at end of file diff --git a/Concentration/README.md b/Concentration/README.md index a2af49d4a6..40f0acf5d8 100644 --- a/Concentration/README.md +++ b/Concentration/README.md @@ -77,4 +77,7 @@ If you use the 5e OGL character sheet, it can also automaticly add the concentra * Remove statusmarker from all objects when removed. **25-04-2018 - 0.1.5** -* Correct whisper target on spell cast concentration check. \ No newline at end of file +* Correct whisper target on spell cast concentration check. + +**02-06-2024 - 0.2.0** +* Updated to work with Beacon sheets \ No newline at end of file diff --git a/Concentration/script.json b/Concentration/script.json index 8d32cd856a..4f525eb7a3 100644 --- a/Concentration/script.json +++ b/Concentration/script.json @@ -1,8 +1,14 @@ { "name": "Concentration", "script": "Concentration.js", - "version": "0.1.14", - "previousversions": ["0.1.5", "0.1.8", "0.1.12", "0.1.13"], + "version": "0.2.0", + "previousversions": [ + "0.1.5", + "0.1.8", + "0.1.12", + "0.1.13", + "0.1.14" + ], "description": "Concentration keeps track of characters concentration, and reminds to do a concentration check. If you use the 5e OGL character sheet, it can also automaticly add the concentrating marker when a concentrating spell is cast. ![Concentration Reminder](https://i.imgur.com/zEVJpOH.png \"Concentration Reminder\") ![Spell Cast](https://i.imgur.com/HucNIDc.png \"Spell Cast\") ### Commands * **!concentration** * Shows the config menu without tokens selected. * Toggles concentration on selected tokens. ### Config ![Config Menu](https://i.imgur.com/P2Siu61.png \"Config Menu\") * **Command** - Which command you want to use for this script. * **Statusmarker** - Which statusmarker you want to use for concentration. * **HP Bar** - Which bar do you use as the HP bar? * **Send Reminder To** - To who you want to send the reminder. * **Auto Add Con. Marker** - Automatically add the concentration marker when a concentration spell is cast (works only for the 5e OGL sheet at the moment). * **Auto Roll Save** - If you want to Automatically roll the saving throw. * **Bonus Attribute** - Which attribute to use for the bonus modifier (defaulted to the constitution saving throw for the 5e OGL sheet). ![Auto Roll](https://i.imgur.com/WHUV5iw.png \"Auto Roll\") --- #### Changelog: **0.1.11** * `!concentration` with tokens selected will now toggle the statusmarker on them. **0.1.10** * If you use autoroll, and the save failed, it will automatically remove the statusmarker. **0.1.9** * Auto Roll Saves (Optional) **01-05-2018 - 0.1.8** * Bugfix. **28-04-2018 - 0.1.7** * Remove statusmarker from all objects when removed. **25-04-2018 - 0.1.5** * Correct whisper target on spell cast concentration check.", "authors": "Robin Kuiper", "roll20userid": "1226016",