/*
* Version 0.1.12
* 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;
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.auto_roll_save){
let whisper = (target !== '') ? '/w ' + target : '';
//&{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+'}}
sendChat(script_name, '[[1d20cf<'+(DC-con_save_mod-1)+'cs>'+(DC-con_save_mod-1)+'+'+con_save_mod+']]', results => {
let rollresult = results[0].inlinerolls[0].results.rolls[0].results[0].v,
total = rollresult + con_save_mod,
success = total >= DC,
result_text = (success) ? 'Success' : 'Failed',
result_color = (success) ? 'green' : 'red',
title = 'Concentration Save
'+obj.get('name')+'';
let contents = ' \
| DC | \'+DC+' | \
|---|---|
| Modifier | \'+con_save_mod+' | \
| Roll Result | \'+rollresult+' | \
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('Bonus Attribute: ' + bonusAttrButton) } 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`.