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.+?>(.*?))[1];
+ spell_name = msg.content.match(/header__title.+?>(.*?))[1];
+ } else {
+ character_name = msg.content.match(/charname=([^\n{}]*[^"\n{}])/);
+ character_name = RegExp.$1;
+ 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 = 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 = '';
+ items.forEach((item) => {
+ list += '- '+item+'
';
+ });
+ 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.+?>(.*?))[1];
+ spell_name = msg.content.match(/header__title.+?>(.*?))[1];
+ } else {
+ character_name = msg.content.match(/charname=([^\n{}]*[^"\n{}])/);
+ character_name = RegExp.$1;
+ spell_name = msg.content.match(/name=([^\n{}]*[^"\n{}])/);
+ spell_name = RegExp.$1;
+ }
- 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' }),
@@ -183,7 +192,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);
@@ -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.   ### Commands * **!concentration** * Shows the config menu without tokens selected. * Toggles concentration on selected tokens. ### Config  * **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).  --- #### 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",