diff --git a/Fetch/2.1.2/Fetch.js b/Fetch/2.1.2/Fetch.js new file mode 100644 index 0000000000..d3342b3d33 --- /dev/null +++ b/Fetch/2.1.2/Fetch.js @@ -0,0 +1,1759 @@ +/* +========================================================= +Name : Fetch +GitHub : https://github.com/TimRohr22/Cauldron/tree/master/Fetch +Roll20 Contact : timmaugh +Version : 2.1.2 +Last Update : 6 OCT 2025 +========================================================= +*/ +var API_Meta = API_Meta || {}; +API_Meta.Fetch = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; +{ try { throw new Error(''); } catch (e) { API_Meta.Fetch.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (12)); } } + +const Fetch = (() => { //eslint-disable-line no-unused-vars + const apiproject = 'Fetch'; + const version = '2.1.2'; + const apilogo = 'https://i.imgur.com/jeIkjvS.png'; + const apilogoalt = 'https://i.imgur.com/boYO3cf.png'; + const schemaVersion = 0.2; + API_Meta[apiproject].version = version; + const vd = new Date(1759763868181); + const versionInfo = () => { + log(`\u0166\u0166 ${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} \u0166\u0166 -- offset ${API_Meta[apiproject].offset}`); + if (!state.hasOwnProperty(apiproject) || state[apiproject].version !== schemaVersion) { //eslint-disable-line no-prototype-builtins + log(` > Updating ${apiproject} Schema to v${schemaVersion} <`); + switch (state[apiproject] && state[apiproject].version) { + + case 0.1: + /* falls through */ + case 0.2: + state[apiproject].settings = { + playerscanids: false + }; + state[apiproject].defaults = { + playerscanids: false + } + /* falls through */ + case 'UpdateSchemaVersion': + state[apiproject].version = schemaVersion; + break; + + default: + state[apiproject] = { + version: schemaVersion, + settings: { + playerscanids: false + }, + defaults: { + playerscanids: false + } + }; + break; + } + } + }; + const logsig = () => { + // initialize shared namespace for all signed projects, if needed + state.torii = state.torii || {}; + // initialize siglogged check, if needed + state.torii.siglogged = state.torii.siglogged || false; + state.torii.sigtime = state.torii.sigtime || Date.now() - 3001; + if (!state.torii.siglogged || Date.now() - state.torii.sigtime > 3000) { + const logsig = '\n' + + ' _____________________________________________ ' + '\n' + + ' )_________________________________________( ' + '\n' + + ' )_____________________________________( ' + '\n' + + ' ___| |_______________| |___ ' + '\n' + + ' |___ _______________ ___| ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + '______________|_|_______________|_|_______________' + '\n' + + ' ' + '\n'; + log(`${logsig}`); + state.torii.siglogged = true; + state.torii.sigtime = Date.now(); + } + return; + }; + // ================================================== + // STATE MANAGEMENT + // ================================================== + const manageState = { // eslint-disable-line no-unused-vars + reset: () => state[apiproject].settings = _.clone(state[apiproject].defaults), + set: (p, v) => state[apiproject].settings[p] = v, + get: (p) => { return state[apiproject].settings[p]; } + }; + + // ================================================== + // UTILTIES + // ================================================== + const generateUUID = (() => { + let a = 0; + let b = []; + + return () => { + let c = (new Date()).getTime() + 0; + let f = 7; + let e = new Array(8); + let d = c === a; + a = c; + for (; 0 <= f; f--) { + e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); + c = Math.floor(c / 64); + } + c = e.join(""); + if (d) { + for (f = 11; 0 <= f && 63 === b[f]; f--) { + b[f] = 0; + } + b[f]++; + } else { + for (f = 0; 12 > f; f++) { + b[f] = Math.floor(64 * Math.random()); + } + } + for (f = 0; 12 > f; f++) { + c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); + } + return c; + }; + })(); + + const escapeRegExp = (string) => { return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); }; + const getfirst = (cmd, ...args) => { + // pass in objects of form: {type: 'text', rx: /regex/} + // return object of form : {regex exec object with property 'type': 'text'} + + let ret = {}; + let r; + args.find(a => { + r = a.rx.exec(cmd); + if (r && (!ret.length || r.index < ret.index)) { + ret = Object.assign(r, { type: a.type }); + } + a.lastIndex = 0; + }, ret); + return ret; + }; + + // ================================================== + // PRESENTATION + // ================================================== + + let html = {}; + let css = {}; // eslint-disable-line no-unused-vars + let HE = () => { }; // eslint-disable-line no-unused-vars + const theme = { + primaryColor: '#5E0099', + primaryTextColor: '#232323', + primaryTextBackground: '#ededed' + } + const localCSS = { + msgheader: { + 'background-color': theme.primaryColor, + 'color': 'white', + 'font-size': '1.2em', + 'padding-left': '4px' + }, + msgbody: { + 'color': theme.primaryTextColor, + 'background-color': theme.primaryTextBackground + }, + msgfooter: { + 'color': theme.primaryTextColor, + 'background-color': theme.primaryTextBackground + }, + msgheadercontent: { + 'display': 'table-cell', + 'vertical-align': 'middle', + 'padding': '4px 8px 4px 6px' + }, + msgheaderlogodiv: { + 'display': 'table-cell', + 'max-height': '30px', + 'margin-right': '8px', + 'margin-top': '4px', + 'vertical-align': 'middle' + }, + logoimg: { + 'background-color': 'transparent', + 'float': 'left', + 'border': 'none', + 'max-height': '30px' + }, + boundingcss: { + 'background-color': theme.primaryTextBackground + }, + inlineEmphasis: { + 'font-weight': 'bold' + } + } + const msgbox = ({ + msg: msg = '', + title: title = '', + headercss: headercss = localCSS.msgheader, + bodycss: bodycss = localCSS.msgbody, + footercss: footercss = localCSS.msgfooter, + sendas: sendas = 'Fetch', + whisperto: whisperto = '', + footer: footer = '', + btn: btn = '', + } = {}) => { + if (title) title = html.div(html.div(html.img(apilogoalt, 'SelectManager Logo', localCSS.logoimg), localCSS.msgheaderlogodiv) + html.div(title, localCSS.msgheadercontent), {}); + Messenger.MsgBox({ msg: msg, title: title, bodycss: bodycss, sendas: sendas, whisperto: whisperto, footer: footer, btn: btn, headercss: headercss, footercss: footercss, boundingcss: localCSS.boundingcss, noarchive: true }); + }; + + const getWhisperTo = (who) => who.toLowerCase() === 'api' ? 'gm' : who.replace(/\s\(gm\)$/i, ''); + const handleConfig = msg => { + if (msg.type !== 'api' || !/^!fetchconfig/.test(msg.content)) return; + let recipient = getWhisperTo(msg.who); + if (!playerIsGM(msg.playerid)) { + msgbox({ title: 'GM Rights Required', msg: 'You must be a GM to perform that operation', whisperto: recipient }); + return; + } + let cfgrx = /^(\+|-)(playerscanids)$/i; + let res; + let cfgTrack = {}; + let message; + if (/^!fetchconfig\s+[^\s]/.test(msg.content)) { + msg.content.split(/\s+/).slice(1).forEach(a => { + res = cfgrx.exec(a); + if (!res) return; + if (res[2].toLowerCase() === 'playerscanids') { + manageState.set('playerscanids', (res[1] === '+')); + cfgTrack[res[2]] = res[1]; + } + }); + let changes = Object.keys(cfgTrack).map(k => `${html.span(k, localCSS.inlineEmphasis)}: ${cfgTrack[k] === '+' ? 'enabled' : 'disabled'}`).join('
'); + msgbox({ title: `Fetch Config Changed`, msg: `You have made the following changes to the Fetch configuration:
${changes}`, whisperto: recipient }); + } else { + cfgTrack.playerscanids = `${html.span('playerscanids', localCSS.inlineEmphasis)}: ${manageState.get('playerscanids') ? 'enabled' : 'disabled'}`; + message = `Fetch is currently configured as follows:
${cfgTrack.playerscanids}`; + msgbox({ title: 'Fetch Configuration', msg: message, whisperto: recipient }); + } + }; + + // ================================================== + // PROCESS + // ================================================== + + const repeatingOrdinal = (character_id, section = '', attr_name = '') => { + if (!section && !attr_name) return; + let ordrx, match; + if (attr_name) { + ordrx = /^repeating_([^_]+)_([^_]+)_.*$/; + if (!ordrx.test(attr_name)) return; // the supplied attribute name isn't a repeating attribute at all + match = ordrx.exec(attr_name); + section = match[1]; + } + let sectionrx = new RegExp(`repeating_${section}_([^_]+)_.*$`); + let createOrderKeys = [...new Set(findObjs({ type: 'attribute', characterid: character_id }) + .filter(a => sectionrx.test(a.get('name'))) + .map(a => sectionrx.exec(a.get('name'))[1]))]; + let sortOrderKeys = (findObjs({ type: 'attribute', characterid: character_id, name: `_reporder_repeating_${section}` })[0] || { get: () => { return ''; } }) + .get('current') + .split(/\s*,\s*/) + .filter(a => createOrderKeys.includes(a)); + sortOrderKeys.push(...createOrderKeys.filter(a => !sortOrderKeys.includes(a))); + return attr_name ? sortOrderKeys.indexOf(match[2]) : sortOrderKeys; + }; + const parsePattern = (cmd) => { + const fieldcomprx = /^((?m)\s*\|)?\s*(?[^\s]+?)\s*(?>=|<=|~|!~|=|!=|<|>)\s*((`|'|")(?.*?)\6|(?.*?)(?=\s|$))\s*/i; + const fieldrx = /^((?m)\s*\|)?\s*(?[^\s]+)\s*/i; + const fieldcomptm = { rx: fieldcomprx, type: 'fieldcomp' }, + fieldtm = { rx: fieldrx, type: 'field' }; + let index = 0; + let p = {}; + let tokens = []; + while (!/^$/.test(cmd.slice(index))) { + p = getfirst(cmd.slice(index), fieldcomptm, fieldtm); + if (p) { + if (p.type === 'field') tokens.push({ type: '=', contents: [p.groups.field, true], retrieve: p.groups.retrieve ? 'max' : 'current' }); + else tokens.push({ type: p.groups.operator, contents: [p.groups.field, p.groups.value || p.groups.altvalue], retrieve: p.groups.retrieve ? 'max' : 'current' }); + index += p[0].length; + } else { + return { tokens: [], error: `Unexpected token encountered in repeating pattern: ${cmd}` }; + } + } + return { tokens: tokens }; + }; + + const getSheetItem = (res, pid, char) => { + // expects result of the getFirst() function, a rx result with a type property + // r.type === 'sheetitem' + const itemTypeLib = { + '@': 'attribute', + '*': 'attribute', + '%': 'ability' + } + let c = char || getChar(res.groups.character, pid); + if (!c) return; + c.id = c.id || c._id; + // standard sheet items + if (['@', '%'].includes(res.groups.type)) { + return findObjs({ type: itemTypeLib[res.groups.type], characterid: c.id }) + .filter(a => a.get('name') === res.groups.item)[0]; + } + // if we're still here, we're looking for a repeating item + // test if they used a full form with an ID or a $0 form + if (res.groups.type === '*' && res.groups.reference && res.groups.reference.length) { + let rowid = /\$\d+/.test(res.groups.reference) ? repeatingOrdinal(c.id, res.groups.section)[/\$(\d+)/.exec(res.groups.reference)[1]] : res.groups.reference; + return rowid ? findObjs({ type: itemTypeLib[res.groups.type], characterid: c.id }) + .filter(a => a.get('name') === `repeating_${res.groups.section}_${rowid}_${res.groups.valuesuffix}`)[0] : rowid; + } + // if we're still here, they used a pattern match + let p = parsePattern(res.groups.pattern); + if (!p.tokens.length) { + log(p.error || 'No pattern detected for repeating sheet item.'); + return; + } + + let filterLib = { + '=': (a) => a.contents[0] == a.contents[1], // eslint-disable-line eqeqeq + '!=': (a) => a.contents[0] != a.contents[1],// eslint-disable-line eqeqeq + '~': (a) => a.contents[0].includes(a.contents[1]), + '!~': (a) => !a.contents[0].includes(a.contents[1]), + '>': (a) => (internalTestLib.num(a.contents[0]) ? Number(a.contents[0]) : a.contents[0]) > (internalTestLib.num(a.contents[1]) ? Number(a.contents[1]) : a.contents[1]), + '>=': (a) => (internalTestLib.num(a.contents[0]) ? Number(a.contents[0]) : a.contents[0]) >= (internalTestLib.num(a.contents[1]) ? Number(a.contents[1]) : a.contents[1]), + '<': (a) => (internalTestLib.num(a.contents[0]) ? Number(a.contents[0]) : a.contents[0]) < (internalTestLib.num(a.contents[1]) ? Number(a.contents[1]) : a.contents[1]), + '<=': (a) => (internalTestLib.num(a.contents[0]) ? Number(a.contents[0]) : a.contents[0]) <= (internalTestLib.num(a.contents[1]) ? Number(a.contents[1]) : a.contents[1]) + } + + p.tests = []; + let reprx = new RegExp(`^repeating_${res.groups.section}_(?[^_]*?)_(?.+)$`); + let repres; + let o = findObjs({ type: itemTypeLib[res.groups.type], characterid: c.id }) + .filter(a => reprx.test(a.get('name'))); + o.forEach(a => { + reprx.lastIndex = 0; + repres = reprx.exec(a.get('name')); + a.name = a.get('name'); + a.repID = repres.groups.repID; + a.suffix = repres.groups.suffix; + }); + + let viable = []; + p.tokens.forEach(s => { + viable = []; + o.forEach(a => { + if (a.suffix.toLowerCase() === s.contents[0].toLowerCase()) { + if (filterLib[s.type]({ contents: [a.get(s.retrieve), s.contents[1]] })) viable.push(a.repID); + } + }); + p.tests.push(viable); + }); + // we should have the same number of tests as we do testable conditions + if (p.tests.length !== p.tokens.length) { + log(`EXITING: TEST COUNTS DON'T MATCH`); + return; + } + viable = p.tests.reduce((m, v) => m.filter(repID => v.includes(repID))); + if (viable.length) { + let retObj = findObjs({ type: itemTypeLib[res.groups.type], characterid: c.id }) + .filter(a => a.get('name') === `repeating_${res.groups.section}_${viable[0]}_${res.groups.valuesuffix}`)[0]; + return retObj; + } + }; + const getSheetItemVal = (res, pid, char) => { + // expects the result of a rx with groups + let val = '', + retrieve = '', + o = {}; + // determine what to test; also what to retrieve if another value isn't specified + if (['@', '*'].includes(res.groups.type) && res.groups.valtype !== 'max') { + retrieve = 'current'; + } else if (['@', '*'].includes(res.groups.type)) { + retrieve = 'max'; + } else { + retrieve = 'action'; + } + // determine if a different retrievable info is requested + if (res.groups.type === '*' && res.groups.valtype === 'name$') { + retrieve = 'name$'; + } else if (res.groups.type === '*' && res.groups.valtype === 'row$') { + retrieve = 'row$'; + } else if (res.groups.valtype === 'rowid') { + retrieve = 'rowid'; + } else if (res.groups.valtype === 'name') { + retrieve = 'name'; + } else if (res.groups.valtype === 'id') { + retrieve = 'id'; + } + // go get the item + o = getSheetItem(res, pid, char); + if (!o) { + return; + } else { + if (['name', 'action', 'current', 'max', 'id'].includes(retrieve)) { + val = o.get(retrieve); + } else { + val = o.get('name'); + let row; + let rptrx = /^repeating_([^_]+)_([^_]+)_(.*)$/i; + let rptres = rptrx.exec(val) || [undefined, undefined, '', '']; + switch (retrieve) { + case 'row$': + val = `$${repeatingOrdinal(o.get('characterid'), undefined, val)}`; + break; + case 'name$': + row = `$${repeatingOrdinal(o.get('characterid'), undefined, val)}`; + val = `repeating_${rptres[1]}_${row}_${rptres[3]}`; + break; + case 'rowid': + val = rptres[2]; + break; + default: + } + } + } + return val; + }; + + const getChar = (query, pid) => { // find a character where query is an identifying piece of information (id, name, or token id) + let character; + if (typeof query !== 'string') return character; + let qrx = new RegExp(escapeRegExp(query), 'i'); + let charsIControl = findObjs({ type: 'character' }); + charsIControl = playerIsGM(pid) || manageState.get('playerscanids') ? charsIControl : charsIControl.filter(c => { + return c.get('controlledby').split(',').reduce((m, p) => { + return m || p === 'all' || p === pid; + }, false) + }); + character = charsIControl.filter(c => c.id === query)[0] || + charsIControl.filter(c => c.id === (getObj('graphic', query) || { get: () => { return '' } }).get('represents'))[0] || + charsIControl.filter(c => c.get('name') === query)[0] || + charsIControl.filter(c => { + qrx.lastIndex = 0; + return qrx.test(c.get('name')); + })[0]; + return character; + }; + const getPageForPlayer = (playerid) => { + let player = getObj('player', playerid); + if (playerIsGM(playerid)) { + return player.get('lastpage') || Campaign().get('playerpageid'); + } + + let psp = Campaign().get('playerspecificpages'); + if (psp[playerid]) { + return psp[playerid]; + } + + return Campaign().get('playerpageid'); + }; + const getPagesForAllPlayers = () => findObjs({ type: 'player', online: true }) + .reduce((m, p) => { + m[p.id] = getPageForPlayer(p.id) + return m; + }, {}); + const getCampaign = () => { + let c = simpleObj(Campaign()); + let p = getPagesForAllPlayers(); + + c.sheetname = Campaign().sheetName; + c.nodeversion = Campaign().nodeVersion; + + c.currentpages = Object.keys(p).map(k => `${k}:${p[k]}`).join(','); + c.currentpagesname = Object.keys(p).map(k => `${getObjName(k,'player')}:${getObjName(p[k],'page')}`).join(','); + return c; + }; + const getPlayer = (query) => { + let player = findObjs({ type: 'player', id: query })[0] || + findObjs({ type: 'player' }).filter(p => { return [query.toLowerCase(), query.replace(/\s\(gm\)$/i, '').toLowerCase()].includes(p.get('_displayname').toLowerCase()); })[0]; + + if (player && player.id) { + player = simpleObj(player); + player.currentpage = getPageForPlayer(player._id); + } + return player; + }; + const getPage = (query) => { + return findObjs({ type: 'page', id: query })[0] || + findObjs({ type: 'page' }).filter(p => { return p.get('name') === query; })[0]; + }; + const getHandout = (query, pid) => { + let handout; + if (typeof query !== 'string') return handout; + let qrx = new RegExp(escapeRegExp(query), 'i'); + + let handoutsIControl = findObjs({ type: 'handout' }); + + handoutsIControl = playerIsGM(pid) || manageState.get('playerscanids') ? handoutsIControl : handoutsIControl.filter(ho => { + return [...ho.get('inplayerjournals').split(','), ...ho.get('controlledby').split(',')].reduce((m, p) => { + return m || p === 'all' || p === pid; + }, false) + }); + handout = handoutsIControl.filter(ho => ho.id === query)[0] || + handoutsIControl.filter(ho => ho.get('name') === query)[0] || + handoutsIControl.filter(ho => { + qrx.lastIndex = 0; + return qrx.test(ho.get('name')); + })[0]; + return handout; + }; + const getTag = (oid, otype, query, pid) => { + let obj = getObjOrNull(otype, oid); + if (!obj.id) { + if (otype === 'character') { + obj = getChar(oid, pid); + } else if (otype === 'handout') { + obj = getHandout(oid, pid); + } + } + let tags = JSON.parse(obj.get('tags') || JSON.stringify([])); + if (!tags.length || !tags.map(t=>t.toLowerCase()).includes(query.toLowerCase())) { + return { is: 'no', count: 0 }; + } else { + return { is: 'yes', count: tags.filter(t => t === query).length }; + } + }; + + const decomposeStatuses = (list = '') => { + return list.split(/\s*,\s*/g).filter(s => s.length) + .reduce((m, s) => { + let origst = libTokenMarkers.getStatus(s.slice(0, /(@\d+$|:)/.test(s) ? /(@\d+$|:)/.exec(s).index : s.length)); + let st = _.clone(origst); + if (!st) return m; + st.num = /^.+@0*(\d+)/.test(s) ? /^.+@0*(\d+)/.exec(s)[1] : ''; + st.html = origst.getHTML(); + st.url = st.url || ''; + m.push(st); + return m; + }, []); + }; + class StatusBlock { + constructor({ token: token = {}, msgId: msgId = generateUUID() } = {}) { + this.token = token; + this.msgId = msgId; + this.statuses = (decomposeStatuses(token.statusmarkers) || []).reduce((m, s) => { + m[s.name] = m[s.name] || []; + m[s.name].push(Object.assign({}, s, { is: 'yes' })); + let shortTag = s.tag.split(/::/)[0]; + if (shortTag !== s.name) { + m[shortTag] = m[shortTag] || []; + m[shortTag].push(Object.assign({}, s, { is: 'yes' })); + } + return m; + }, {}); + } + } + + const tokenStatuses = {}; + const getStatus = (t, pgid, query, msgId) => { + let token, rxret, status, index, modindex, statusblock; + token = getToken(t, pgid); + if (!token) return; + token = simpleObj(token); + if (token && !token.hasOwnProperty('id')) token.id = token._id; + if (!tokenStatuses.hasOwnProperty(token.id) || tokenStatuses[token.id].msgId !== msgId) { + tokenStatuses[token.id] = new StatusBlock({ token: token, msgId: msgId }); + } + rxret = /(?.+?)(?:\?(?\d+|all\+?))?$/.exec(query); + [status, index] = [rxret.groups.marker, rxret.groups.index]; + if (!index) { + modindex = 1; + } else if (['all', 'all+'].includes(index.toLowerCase())) { + modindex = index.toLowerCase(); + } else { + modindex = Number(index); + } + statusblock = tokenStatuses[token.id].statuses[status]; + if (!statusblock || !statusblock.length) { + return { is: 'no', count: '0' }; + }; + switch (index) { + case 'all': + return statusblock.reduce((m, sm) => { + m.num = `${m.num || ''}${sm.num}`; + m.tag = m.tag || sm.tag; + m.url = m.url || sm.url; + m.html = m.html || sm.html; + m.is = 'yes'; + m.count = m.count || statusblock.length; + return m; + }, {}); + case 'all+': + return statusblock.reduce((m, sm) => { + m.num = `${Number(m.num || 0) + Number(sm.num)}`; + m.tag = m.tag || sm.tag; + m.url = m.url || sm.url; + m.html = m.html || sm.html; + m.is = 'yes'; + m.count = m.count || statusblock.length; + return m; + }, {}); + default: + if (statusblock.length >= modindex) { + return Object.assign({}, statusblock[modindex - 1], { count: index ? '1' : statusblock.length }); + } else { + return { is: 'no', 'count': '0' }; + } + } + }; + const getMarker = (query) => { + if (libTokenMarkers.getStatus(query).getTag().length) return decomposeStatuses(query)[0]; + }; + + const getPageID = (pid) => { + return (pid && playerIsGM(pid)) ? (getObj('player', pid).get('_lastpage') || Campaign().get('playerpageid')) : Campaign().get('playerpageid'); + }; + const getTrackerVal = (token) => { + let retval = {}; + let to = JSON.parse(Campaign().get('turnorder') || '[]'); + let mto = to.map(t => t.id); + if (mto.includes(token.id)) { + retval.tracker = to.filter(t => t.id === token.id)[0].pr; + retval.tracker_offset = mto.indexOf(token.id); + } + return retval; + }; + const getCard = (info) => { + let card = findObjs({ type: 'card', id: info })[0] || + findObjs({ type: 'card', name: info })[0] || + findObjs({ id: (findObjs({ type: 'graphic', subtype: 'card', id: info })[0] || { get: () => '' }).get('cardid') })[0]; + return card; + }; + const getTable = (query, pid) => { + let table; + if (typeof query !== 'string') return table; + let qrx = new RegExp(escapeRegExp(query), 'i'); + + let tablesIControl = findObjs({ type: 'rollabletable' }); + + tablesIControl = playerIsGM(pid) || manageState.get('playerscanids') ? tablesIControl : tablesIControl.filter(tbl => tbl.get('showplayers')); + table = tablesIControl.filter(tbl => tbl.id === query)[0] || + tablesIControl.filter(tbl => tbl.get('name') === query)[0] || + tablesIControl.filter(tbl => { + qrx.lastIndex = 0; + return qrx.test(tbl.get('name')); + })[0]; + if (table && table.id) { + table = simpleObj(table); + let items = findObjs({ type: 'tableitem', rollabletableid: table.id }); + table.totalweight = items.reduce((m, v) => m += v.get('weight'), 0); + } + return table; + }; + const getTableItems = (query, tbl, pid) => { + let table; + if (typeof tbl === 'string') { + table = getTable(tbl, pid); + } else { + table = tbl; + } + if (table && table.id) { + let allitems = findObjs({ type: 'tableitem', rollabletableid: table.id }); + let item = allitems.filter(ti => ti.id === query)[0] || + allitems.filter(ti => ti.get('name') === query)[0]; + + if (item && item.id) { return item; } + + let weightedItems = allitems.reduce((m, v) => { + m = [...m, ...new Array(v.get('weight')).fill().map(e => v)]; + return m; + }, []); + let index; + if (['1dw', '1dweight'].includes(query.toLowerCase())) { + index = randomInteger(weightedItems.length) - 1; + return weightedItems[index]; + } else if (!isNaN(parseInt(query))) { + + index = parseInt(query); + if (index < 1) { + index = 1; + } else if (index > weightedItems.length) { + index = weightedItems.length; + } + return weightedItems[index-1]; + } + } + + }; + const getToken = (info, pgid = '') => { + let lightvals = { + base: {}, + assign: {} + }; + let token = findObjs({ type: 'graphic', subtype: 'token', id: info })[0] || + findObjs({ type: 'graphic', subtype: 'card', id: info })[0] || + findObjs({ type: 'graphic', subtype: 'token', name: info, pageid: pgid })[0] || + findObjs({ type: 'graphic', subtype: 'token', pageid: pgid }) + .filter(t => t.get('represents').length && findObjs({ type: 'character', id: t.get('represents') })[0].get('name') === info)[0]; + if (!token) { + let tokensOfName = findObjs({ type: 'graphic', subtype: 'token', name: info }); + if (tokensOfName.length === 1) { + token = tokensOfName[0]; + } + } + if (token && token.id) { + if (typeof checkLightLevel !== 'undefined' && checkLightLevel.hasOwnProperty('isLitBy') && typeof checkLightLevel.isLitBy === 'function') { + lightvals.base = checkLightLevel.isLitBy(token); + lightvals.assign.checklight_isbright = lightvals.base.bright ? 'true' : 'false'; + lightvals.assign.checklight_total = lightvals.base.total + } + token = Object.assign(simpleObj(token), getTrackerVal(token),lightvals.assign); + } + return token; + }; + class nullObj { + constructor() { + this.get = function () { return undefined; } + } + } + const getObjOrNull = (type, id) => { + return (getObj(type, id) || new nullObj()); + }; + const getObjName = (key, type) => { + let o; + switch (type) { + case 'playerlist': + o = key.split(/\s*,\s*/) + .map(k => k === 'all' ? k : getObj('player', k)) + .filter(c => c) + .map(c => c === 'all' ? c : c.get('displayname')) + .join(', '); + return o.length ? o : undefined; + case 'player': + o = getObj(type, key); + return o ? o.get('displayname') : undefined; + case 'deck': + o = getObjOrNull(type, getObjOrNull('card', key)); + return o ? o.get('name') : undefined; + case 'card': + case 'page': + case 'attribute': + case 'character': + default: + o = getObj(type, key); + return o ? o.get('name') : undefined; + } + }; + const getControlledByList = (s, d) => { + if (!s.represents || !s.represents.length) return d && d.length ? d : s.controlledby; + let c = getObj('character', s.represents); + if (c) return c.get('controlledby'); + }; + const tokenProps = { + id: { refersto: '_id', dataval: (d) => d }, + tid: { refersto: '_id', dataval: (d) => d }, + token_id: { refersto: '_id', dataval: (d) => d }, + token_name: { refersto: 'name', dataval: (d) => d }, + page_id: { refersto: '_pageid', dataval: (d) => d }, + pageid: { refersto: '_pageid', dataval: (d) => d }, + pid: { refersto: '_pageid', dataval: (d) => d }, + token_page_id: { refersto: '_pageid', dataval: (d) => d }, + token_pageid: { refersto: '_pageid', dataval: (d) => d }, + token_pid: { refersto: '_pageid', dataval: (d) => d }, + page: { refersto: '_pageid', dataval: d => getObjName(d, 'page') }, + page_name: { refersto: '_pageid', dataval: d => getObjName(d, 'page') }, + sub: { refersto: '_subtype', dataval: (d) => d }, + subtype: { refersto: '_subtype', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + token_type: { refersto: '_type', dataval: (d) => d }, + adv_fow_view_distance: { refersto: 'adv_fow_view_distance', dataval: (d) => d }, + aura1: { refersto: 'aura1_color', dataval: (d) => d }, + aura1_color: { refersto: 'aura1_color', dataval: (d) => d }, + aura1_radius: { refersto: 'aura1_radius', dataval: (d) => d }, + radius1: { refersto: 'aura1_radius', dataval: (d) => d }, + aura1_square: { refersto: 'aura1_square', dataval: (d) => d }, + square1: { refersto: 'aura1_square', dataval: (d) => d }, + aura2: { refersto: 'aura2_color', dataval: (d) => d }, + aura2_color: { refersto: 'aura2_color', dataval: (d) => d }, + aura2_radius: { refersto: 'aura2_radius', dataval: (d) => d }, + radius2: { refersto: 'aura2_radius', dataval: (d) => d }, + aura2_square: { refersto: 'aura2_square', dataval: (d) => d }, + square2: { refersto: 'aura2_square', dataval: (d) => d }, + bar_location: { refersto: 'bar_location', dataval: (d) => d }, + bar_loc: { refersto: 'bar_location', dataval: (d) => d }, + bar1_link: { refersto: 'bar1_link', dataval: (d) => d }, + link1: { refersto: 'bar1_link', dataval: (d) => d }, + bar1_name: { refersto: 'bar1_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + name1: { refersto: 'bar1_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + bar1_max: { refersto: 'bar1_max', dataval: (d) => d }, + max1: { refersto: 'bar1_max', dataval: (d) => d }, + bar1: { refersto: 'bar1_value', dataval: (d) => d }, + bar1_current: { refersto: 'bar1_value', dataval: (d) => d }, + bar1_value: { refersto: 'bar1_value', dataval: (d) => d }, + bar2_link: { refersto: 'bar2_link', dataval: (d) => d }, + link2: { refersto: 'bar2_link', dataval: (d) => d }, + bar2_name: { refersto: 'bar2_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + name2: { refersto: 'bar2_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + bar2_max: { refersto: 'bar2_max', dataval: (d) => d }, + max2: { refersto: 'bar2_max', dataval: (d) => d }, + bar2: { refersto: 'bar2_value', dataval: (d) => d }, + bar2_current: { refersto: 'bar2_value', dataval: (d) => d }, + bar2_value: { refersto: 'bar2_value', dataval: (d) => d }, + bar3_link: { refersto: 'bar3_link', dataval: (d) => d }, + link3: { refersto: 'bar3_link', dataval: (d) => d }, + bar3_name: { refersto: 'bar3_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + name3: { refersto: 'bar3_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + bar3_max: { refersto: 'bar3_max', dataval: (d) => d }, + max3: { refersto: 'bar3_max', dataval: (d) => d }, + bar3: { refersto: 'bar3_value', dataval: (d) => d }, + bar3_current: { refersto: 'bar3_value', dataval: (d) => d }, + bar3_value: { refersto: 'bar3_value', dataval: (d) => d }, + bar4_link: { refersto: 'bar4_link', dataval: (d) => d }, + link4: { refersto: 'bar4_link', dataval: (d) => d }, + bar4_name: { refersto: 'bar4_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + name4: { refersto: 'bar4_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + bar4_max: { refersto: 'bar4_max', dataval: (d) => d }, + max4: { refersto: 'bar4_max', dataval: (d) => d }, + bar4: { refersto: 'bar4_value', dataval: (d) => d }, + bar4_current: { refersto: 'bar4_value', dataval: (d) => d }, + bar4_value: { refersto: 'bar4_value', dataval: (d) => d }, + bright_light_distance: { refersto: 'bright_light_distance', dataval: (d) => d }, + cardid: { refersto: '_cardid', dataval: (d) => d }, + cid: { refersto: '_cardid', dataval: (d) => d }, + card_back: { refersto: '_cardid', dataval: (d) => getObjOrNull('card', d).get('card_back') }, + cardname: { refersto: '_cardid', dataval: (d) => getObjName(d, 'card') }, + + checklight_isbright: { refersto: 'checklight_isbright', dataval: (d) => d }, + checklight_total: { refersto: 'checklight_total', dataval: (d) => d }, + + compact_bar: { refersto: 'compact_bar', dataval: (d) => d }, + currentside: { refersto: 'currentSide', dataval: (d) => d }, + curside: { refersto: 'currentSide', dataval: (d) => d }, + side: { refersto: 'currentSide', dataval: (d) => d }, + deckid: { refersto: '_cardid', dataval: (d) => getObjOrNull('card', d).get('deckid') }, + deckname: { refersto: '_cardid', dataval: (d) => getObjOrNull('deck', getObjOrNull('card', d).get('deckid')).get('name') }, + player: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d).split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all' && getObj('player', a))[0] }, + player_name: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d).split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all').map(a => getObjName(a, 'player')).filter(a => a)[0] }, + token_cby: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d) }, + token_controlledby: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d) }, + token_cby_names: { refersto: 'controlledby', dataval: (d, s) => getObjName(getControlledByList(s, d), 'playerlist') }, + token_controlledby_names: { refersto: 'controlledby', dataval: (d, s) => getObjName(getControlledByList(s, d), 'playerlist') }, + token_cby_name: { refersto: 'controlledby', dataval: (d, s) => getObjName(getControlledByList(s, d), 'playerlist') }, + token_controlledby_name: { refersto: 'controlledby', dataval: (d, s) => getObjName(getControlledByList(s, d), 'playerlist') }, + dim_light_opacity: { refersto: 'dim_light_opacity', dataval: (d) => d }, + directional_bright_light_center: { refersto: 'directional_bright_light_center', dataval: (d) => d }, + directional_bright_light_total: { refersto: 'directional_bright_light_total', dataval: (d) => d }, + directional_low_light_center: { refersto: 'directional_low_light_center', dataval: (d) => d }, + directional_low_light_total: { refersto: 'directional_low_light_total', dataval: (d) => d }, + emits_bright: { refersto: 'emits_bright_light', dataval: (d) => d }, + emits_bright_light: { refersto: 'emits_bright_light', dataval: (d) => d }, + emits_low: { refersto: 'emits_low_light', dataval: (d) => d }, + emits_low_light: { refersto: 'emits_low_light', dataval: (d) => d }, + fliph: { refersto: 'fliph', dataval: (d) => d }, + flipv: { refersto: 'flipv', dataval: (d) => d }, + gmnotes: { refersto: 'gmnotes', dataval: (d) => unescape(d) }, + has_bright_light_vision: { refersto: 'has_bright_light_vision', dataval: (d) => d }, + has_directional_bright_light: { refersto: 'has_directional_bright_light', dataval: (d) => d }, + has_directional_low_light: { refersto: 'has_directional_low_light', dataval: (d) => d }, + has_limit_field_of_night_vision: { refersto: 'has_limit_field_of_night_vision', dataval: (d) => d }, + has_limit_field_of_vision: { refersto: 'has_limit_field_of_vision', dataval: (d) => d }, + has_night_vision: { refersto: 'has_night_vision', dataval: (d) => d }, + has_nv: { refersto: 'has_night_vision', dataval: (d) => d }, + nv_has: { refersto: 'has_night_vision', dataval: (d) => d }, + height: { refersto: 'height', dataval: (d) => d }, + img: { refersto: 'imgsrc', dataval: (d) => `` }, + imgsrc: { refersto: 'imgsrc', dataval: (d) => d }, + imgsrc_short: { refersto: 'imgsrc', dataval: (d) => d.slice(0, Math.max(d.indexOf(`?`), 0) || d.length) }, + drawing: { refersto: 'isdrawing', dataval: (d) => d }, + isdrawing: { refersto: 'isdrawing', dataval: (d) => d }, + lastmove: { refersto: 'lastmove', dataval: (d) => d }, + lastx: { refersto: 'lastmove', dataval: d => d.split(/\s*,\s*/)[0] || '' }, + lasty: { refersto: 'lastmove', dataval: d => d.split(/\s*,\s*/)[1] || '' }, + layer: { refersto: 'layer', dataval: (d) => d }, + left: { refersto: 'left', dataval: (d) => d }, + light_angle: { refersto: 'light_angle', dataval: (d) => d }, + light_dimradius: { refersto: 'light_dimradius', dataval: (d) => d }, + light_hassight: { refersto: 'light_hassight', dataval: (d) => d }, + light_losangle: { refersto: 'light_losangle', dataval: (d) => d }, + light_multiplier: { refersto: 'light_multiplier', dataval: (d) => d }, + light_otherplayers: { refersto: 'light_otherplayers', dataval: (d) => d }, + light_radius: { refersto: 'light_radius', dataval: (d) => d }, + light_sensitivity_multiplier: { refersto: 'light_sensitivity_multiplier', dataval: (d) => d }, + light_sensitivity_mult: { refersto: 'light_sensitivity_multiplier', dataval: (d) => d }, + limit_field_of_night_vision_center: { refersto: 'limit_field_of_night_vision_center', dataval: (d) => d }, + limit_field_of_night_vision_total: { refersto: 'limit_field_of_night_vision_total', dataval: (d) => d }, + limit_field_of_vision_center: { refersto: 'limit_field_of_vision_center', dataval: (d) => d }, + limit_field_of_vision_total: { refersto: 'limit_field_of_vision_total', dataval: (d) => d }, + low_light_distance: { refersto: 'low_light_distance', dataval: (d) => d }, + night_vision_distance: { refersto: 'night_vision_distance', dataval: (d) => d }, + nv_dist: { refersto: 'night_vision_distance', dataval: (d) => d }, + nv_distance: { refersto: 'night_vision_distance', dataval: (d) => d }, + night_vision_effect: { refersto: 'night_vision_effect', dataval: (d) => d }, + nv_effect: { refersto: 'night_vision_effect', dataval: (d) => d }, + night_vision_tint: { refersto: 'night_vision_tint', dataval: (d) => d }, + nv_tint: { refersto: 'night_vision_tint', dataval: (d) => d }, + playersedit_aura1: { refersto: 'playersedit_aura1', dataval: (d) => d }, + playersedit_aura2: { refersto: 'playersedit_aura2', dataval: (d) => d }, + playersedit_bar1: { refersto: 'playersedit_bar1', dataval: (d) => d }, + playersedit_bar2: { refersto: 'playersedit_bar2', dataval: (d) => d }, + playersedit_bar3: { refersto: 'playersedit_bar3', dataval: (d) => d }, + playersedit_name: { refersto: 'playersedit_name', dataval: (d) => d }, + represents: { refersto: 'represents', dataval: (d) => d }, + reps: { refersto: 'represents', dataval: (d) => d }, + represents_name: { refersto: 'represents', dataval: d => getObjName(d, 'character') }, + reps_name: { refersto: 'represents', dataval: d => getObjName(d, 'character') }, + rotation: { refersto: 'rotation', dataval: (d) => d }, + showname: { refersto: 'showname', dataval: (d) => d }, + showplayers_aura1: { refersto: 'showplayers_aura1', dataval: (d) => d }, + showplayers_aura2: { refersto: 'showplayers_aura2', dataval: (d) => d }, + showplayers_bar1: { refersto: 'showplayers_bar1', dataval: (d) => d }, + showplayers_bar2: { refersto: 'showplayers_bar2', dataval: (d) => d }, + showplayers_bar3: { refersto: 'showplayers_bar3', dataval: (d) => d }, + showplayers_name: { refersto: 'showplayers_name', dataval: (d) => d }, + show_tooltip: { refersto: 'show_tooltip', dataval: (d) => d }, + sides: { refersto: 'sides', dataval: (d) => d }, + sides_short: { refersto: 'sides', dataval: (d) => ('' || d).split(`|`).map(side => decodeURIComponent(side).slice(0, Math.max(side.indexOf(`?`), 0) || side.length)).join(`|`)}, + sidecount: { refersto: 'sides', dataval: (d) => ('' || d).split(`|`).length }, + sidescount: { refersto: 'sides', dataval: (d) => ('' || d).split(`|`).length }, + markers: { refersto: 'statusmarkers', dataval: (d) => d }, + statusmarkers: { refersto: 'statusmarkers', dataval: (d) => d }, + tint: { refersto: 'tint_color', dataval: (d) => d }, + tint_color: { refersto: 'tint_color', dataval: (d) => d }, + tooltip: { refersto: 'tooltip', dataval: (d) => d }, + top: { refersto: 'top', dataval: (d) => d }, + tracker: { refersto: 'tracker', dataval: (d) => d }, + tracker_offset: { refersto: 'tracker_offset', dataval: (d) => d }, + width: { refersto: 'width', dataval: (d) => d } + }; + const charProps = { + char_id: { refersto: '_id', dataval: (d) => d }, + character_id: { refersto: '_id', dataval: (d) => d }, + char_name: { refersto: 'name', dataval: (d) => d }, + character_name: { refersto: 'name', dataval: (d) => d }, + char_type: { refersto: '_type', dataval: (d) => d }, + character_type: { refersto: '_type', dataval: (d) => d }, + avatar: { refersto: 'avatar', dataval: (d) => d }, + char_img: { refersto: 'avatar', dataval: (d) => `` }, + character_img: { refersto: 'avatar', dataval: (d) => `` }, + archived: { refersto: 'archived', dataval: (d) => d }, + inplayerjournals: { refersto: 'inplayerjournals', dataval: (d) => d }, + inplayerjournals_name: { refersto: 'inplayerjournals', dataval: (d) => getObjName(d, 'playerlist') }, + inplayerjournals_names: { refersto: 'inplayerjournals', dataval: (d) => getObjName(d, 'playerlist') }, + character_controlledby: { refersto: 'controlledby', dataval: (d) => d }, + character_cby: { refersto: 'controlledby', dataval: (d) => d }, + player: { refersto: 'controlledby', dataval: (d) => d.split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all' && getObj('player', a))[0] }, + player_name: { refersto: 'controlledby', dataval: (d) => d.split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all').map(a => getObjName(a, 'player')).filter(a => a)[0] }, + char_cby: { refersto: 'controlledby', dataval: (d) => d }, + char_controlledby: { refersto: 'controlledby', dataval: (d) => d }, + character_controlledby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + character_cby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + char_cby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + char_controlledby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + character_controlledby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + character_cby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + char_cby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + char_controlledby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + defaulttoken: { refersto: '_defaulttoken', dataval: (d) => d }, + tags: { refersto: 'tags', dataval: (d) => JSON.parse(d).join(',') } + }; + const playerProps = { // $(player.player_color) + player_id: { refersto: '_id', dataval: (d) => d }, + player_name: { refersto: '_displayname', dataval: (d) => d }, + displayname: { refersto: '_displayname', dataval: (d) => d }, + display_name: { refersto: '_displayname', dataval: (d) => d }, + player_type: { refersto: '_type', dataval: (d) => d }, + color: { refersto: 'color', dataval: (d) => d }, + lastpage: { refersto: '_lastpage', dataval: (d) => d }, + last_page: { refersto: '_lastpage', dataval: (d) => d }, + lastpagename: { refersto: '_lastpage', dataval: (d) => getObjName(d, 'page') }, + last_page_name: { refersto: '_lastpage', dataval: (d) => getObjName(d, 'page') }, + current_page: { refersto: 'currentpage', dataval: (d) => d }, + currentpage: { refersto: 'currentpage', dataval: (d) => d }, + currentpagename: { refersto: 'currentpage', dataval: (d) => getObjName(d, 'page') }, + current_page_name: { refersto: 'currentpage', dataval: (d) => getObjName(d, 'page') }, + macrobar: { refersto: '_macrobar', dataval: (d) => d }, + online: { refersto: '_online', dataval: (d) => d }, + roll20id: { refersto: '_d20userid', dataval: (d) => d }, + roll20_id: { refersto: '_d20userid', dataval: (d) => d }, + r20id: { refersto: '_d20userid', dataval: (d) => d }, + r20_id: { refersto: '_d20userid', dataval: (d) => d }, + showmacrobar: { refersto: 'showmacrobar', dataval: (d) => d }, + show_macrobar: { refersto: 'showmacrobar', dataval: (d) => d }, + speakingas: { refersto: 'speakingas', dataval: (d) => d }, + speaking_as: { refersto: 'speakingas', dataval: (d) => d }, + userid: { refersto: '_d20userid', dataval: (d) => d }, + user_id: { refersto: '_d20userid', dataval: (d) => d } + }; + const pageProps = { // @(page.pagename) + page_id: { refersto: '_id', dataval: (d) => d }, + page_name: { refersto: 'name', dataval: (d) => d }, + page_type: { refersto: '_type', dataval: (d) => d }, + adv_fow_enabled: { refersto: 'adv_fow_enabled', dataval: (d) => d }, + adv_fow_dim_reveals: { refersto: 'adv_fow_dim_reveals', dataval: (d) => d }, + adv_fow_show_grid: { refersto: 'adv_fow_show_grid', dataval: (d) => d }, + archived: { refersto: 'archived', dataval: (d) => d }, + background_color: { refersto: 'background_color', dataval: (d) => d }, + bg_color: { refersto: 'background_color', dataval: (d) => d }, + daylight_mode_enabled: { refersto: 'daylight_mode_enabled', dataval: (d) => d }, + daylightmodeopacity: { refersto: 'daylightModeOpacity', dataval: (d) => d }, + daylight_mode_opacity: { refersto: 'daylightModeOpacity', dataval: (d) => d }, + diagonaltype: { refersto: 'diagonaltype', dataval: (d) => d }, + diagonal_type: { refersto: 'diagonaltype', dataval: (d) => d }, + diagonal: { refersto: 'diagonaltype', dataval: (d) => d }, + dynamic_lighting_enabled: { refersto: 'dynamic_lighting_enabled', dataval: (d) => d }, + explorer_mode: { refersto: 'explorer_mode', dataval: (d) => d }, + fogopacity: { refersto: 'fog_opacity', dataval: (d) => d }, + fog_opacity: { refersto: 'fog_opacity', dataval: (d) => d }, + force_lighting_refresh: { refersto: 'force_lighting_refresh', dataval: (d) => d }, + gridcolor: { refersto: 'gridcolor', dataval: (d) => d }, + grid_color: { refersto: 'gridcolor', dataval: (d) => d }, + grid_labels: { refersto: 'gridlabels', dataval: (d) => d }, + gridlabels: { refersto: 'gridlabels', dataval: (d) => d }, + gridopacity: { refersto: 'grid_opacity', dataval: (d) => d }, + grid_opacity: { refersto: 'grid_opacity', dataval: (d) => d }, + gridtype: { refersto: 'grid_type', dataval: (d) => d }, + grid_type: { refersto: 'grid_type', dataval: (d) => d }, + height: { refersto: 'height', dataval: (d) => d }, + jukeboxtrigger: { refersto: 'jukeboxtrigger', dataval: (d) => d }, + jukebox_trigger: { refersto: 'jukeboxtrigger', dataval: (d) => d }, + lightupdatedrop: { refersto: 'lightupdatedrop', dataval: (d) => d }, + lightenforcelos: { refersto: 'lightenforcelos', dataval: (d) => d }, + lightrestrictmove: { refersto: 'lightrestrictmove', dataval: (d) => d }, + lightglobalillum: { refersto: 'lightglobalillum', dataval: (d) => d }, + path: { refersto: 'path', dataval: (d) => d }, + placement: { refersto: 'placement', dataval: (d) => d }, + scale_number: { refersto: 'scale_number', dataval: (d) => d }, + scale_units: { refersto: 'scale_units', dataval: (d) => d }, + showdarkness: { refersto: 'showdarkness', dataval: (d) => d }, + show_darkness: { refersto: 'showdarkness', dataval: (d) => d }, + showgrid: { refersto: 'showgrid', dataval: (d) => d }, + show_grid: { refersto: 'showgrid', dataval: (d) => d }, + showlighting: { refersto: 'showlighting', dataval: (d) => d }, + show_lighting: { refersto: 'showlighting', dataval: (d) => d }, + snapping_increment: { refersto: 'snapping_increment', dataval: (d) => d }, + use_auto_wrapper: { refersto: 'useAutoWrapper', dataval: (d) => d }, + width: { refersto: 'width', dataval: (d) => d }, + wrapper: { refersto: 'wrapperColor', dataval: (d) => d }, + wrapper_auto_color: { refersto: 'wrapperAutoColor', dataval: (d) => d }, + wrapper_color: { refersto: 'wrapperColor', dataval: (d) => d }, + zorder: { refersto: '_zorder', dataval: (d) => d } + }; + const campaignProps = { // @(campaign.prop) + campaign_id: { refersto: '_id', dataval: (d) => d }, + campaign_type: { refersto: '_type', dataval: (d) => d }, + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + turnorder: { refersto: 'turnorder', dataval: (d) => d }, + initiativepage: { refersto: 'initiativepage', dataval: (d) => d }, + nodeversion: { refersto: 'nodeversion', dataval: (d) => d }, + sandboxversion: { refersto: 'sandboxVersion', dataval: (d) => d }, + pageid: { refersto: 'playerpageid', dataval: (d) => d }, + page_id: { refersto: 'playerpageid', dataval: (d) => d }, + playerpageid: { refersto: 'playerpageid', dataval: (d) => d }, + playerpage_id: { refersto: 'playerpageid', dataval: (d) => d }, + pagename: { refersto: 'playerpageid', dataval: (d) => getObjName(d, 'page') }, + page_name: { refersto: 'playerpageid', dataval: (d) => getObjName(d, 'page') }, + playerpagename: { refersto: 'playerpageid', dataval: (d) => getObjName(d, 'page') }, + playerpage_name: { refersto: 'playerpageid', dataval: (d) => getObjName(d, 'page') }, + playerspecificpages: { refersto: 'playerspecificpages', dataval: (d) => Object.keys(d).map(k => `${k}:${d[k]}`).join(',') }, + playerspecificpagesname: { refersto: 'playerspecificpages', dataval: (d) => Object.keys(d).map(k => `${getObjName(k, 'player')}:${getObjName(d[k], 'page')}`).join(',') }, + playerspecificpages_name: { refersto: 'playerspecificpages', dataval: (d) => Object.keys(d).map(k => `${getObjName(k, 'player')}:${getObjName(d[k], 'page')}`).join(',') }, + currentpages: { refersto: 'currentpages', dataval: (d) => d }, + currentpagesname: { refersto: 'currentpagesname', dataval: (d) => d }, + sheetname: { refersto: 'sheetname', dataval: (d) => d }, + journalfolder: { refersto: '_journalfolder', dataval: (d) => d }, + jukeboxfolder: { refersto: '_jukeboxfolder', dataval: (d) => d }, + jukeboxplaylistplaying: { refersto: '_jukeboxplaylistplaying', dataval: (d) => d }, + token_markers: { refersto: '_token_markers', dataval: (d) => d }, + markers: { refersto: '_token_markers', dataval: (d) => d } + }; + const markerProps = { // derived from the Campaign object + marker_id: { refersto: 'tag', dataval: (d) => d }, + marker_name: { refersto: 'name', dataval: (d) => d }, + tag: { refersto: 'tag', dataval: (d) => d }, + url: { refersto: 'url', dataval: (d) => d }, + html: { refersto: 'html', dataval: (d) => d } + }; + const statusProps = { // derived from a Token object + status_id: { refersto: 'tag', dataval: (d) => d }, + status_name: { refersto: 'name', dataval: (d) => d }, + num: { refersto: 'num', dataval: (d) => d }, + number: { refersto: 'num', dataval: (d) => d }, + value: { refersto: 'num', dataval: (d) => d }, + val: { refersto: 'num', dataval: (d) => d }, + html: { refersto: 'html', dataval: (d) => d }, + tag: { refersto: 'tag', dataval: (d) => d }, + url: { refersto: 'url', dataval: (d) => d }, + is: { refersto: 'is', dataval: (d) => d }, + count: { refersto: 'count', dataval: (d) => d } + }; + const tagProps = { + count: { refersto: 'count', dataval: (d) => d }, + is: { refersto: 'is', dataval: (d) => d } + }; + const textProps = { + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + color: { refersto: 'color', dataval: (d) => d }, + cby: { refersto: 'controlledby', dataval: (d) => d }, + controlledby: { refersto: 'controlledby', dataval: (d) => d }, + cby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + controlledby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + cby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + controlledby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + font_family: { refersto: 'font_family', dataval: (d) => d }, + font_size: { refersto: 'font_size', dataval: (d) => d }, + height: { refersto: 'height', dataval: (d) => d }, + layer: { refersto: 'layer', dataval: (d) => d }, + left: { refersto: 'left', dataval: (d) => d }, + page_id: { refersto: '_pageid', dataval: (d) => d }, + pageid: { refersto: '_pageid', dataval: (d) => d }, + pid: { refersto: '_pageid', dataval: (d) => d }, + page: { refersto: '_pageid', dataval: d => getObjName(d, 'page') }, + page_name: { refersto: '_pageid', dataval: d => getObjName(d, 'page') }, + player: { refersto: 'controlledby', dataval: (d) => d.split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all' && getObj('player', a))[0] }, + player_name: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d).split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all').map(a => getObjName(a, 'player')).filter(a => a)[0] }, + rotation: { refersto: 'rotation', dataval: (d) => d }, + text: { refersto: 'text', dataval: (d) => d }, + top: { refersto: 'top', dataval: (d) => d }, + width: { refersto: 'width', dataval: (d) => d } + }; + const pathProps = { + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + cby: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d) }, + controlledby: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d) }, + cby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + controlledby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + cby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + controlledby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + fill: { refersto: 'fill', dataval: (d) => d }, + height: { refersto: 'height', dataval: (d) => d }, + layer: { refersto: 'layer', dataval: (d) => d }, + left: { refersto: 'left', dataval: (d) => d }, + page_id: { refersto: '_pageid', dataval: (d) => d }, + pageid: { refersto: '_pageid', dataval: (d) => d }, + pid: { refersto: '_pageid', dataval: (d) => d }, + page: { refersto: '_pageid', dataval: d => getObjName(d, 'page') }, + page_name: { refersto: '_pageid', dataval: d => getObjName(d, 'page') }, + path: { refersto: 'path', dataval: (d) => d }, + player: { refersto: 'controlledby', dataval: (d) => d.split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all' && getObj('player', a))[0] }, + player_name: { refersto: 'controlledby', dataval: (d, s) => getControlledByList(s, d).split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all').map(a => getObjName(a, 'player')).filter(a => a)[0] }, + rotation: { refersto: 'rotation', dataval: (d) => d }, + scalex: { refersto: 'scaleX', dataval: (d) => d }, + scaley: { refersto: 'scaleY', dataval: (d) => d }, + stroke: { refersto: 'stroke', dataval: (d) => d }, + stroke_width: { refersto: 'stroke_width', dataval: (d) => d }, + top: { refersto: 'top', dataval: (d) => d }, + width: { refersto: 'width', dataval: (d) => d } + }; + const cardProps = { + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + deckid: { refersto: '_deckid', dataval: (d) => d }, + name: { refersto: 'name', dataval: (d) => d }, + avatar: { refersto: 'avatar', dataval: (d) => d }, + img: { refersto: 'avatar', dataval: (d) => `` }, + imgsrc: { refersto: 'avatar', dataval: (d) => d }, + imgsrc_short: { refersto: 'avatar', dataval: (d) => d.slice(0, Math.max(d.indexOf(`?`), 0) || d.length) }, + card_back: { refersto: 'card_back', dataval: (d) => d }, + is_removed: { refersto: 'is_removed', dataval: (d) => d }, + tooltip: { refersto: 'tooltip', dataval: (d) => d } + }; + const handoutProps = { + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + name: { refersto: 'name', dataval: (d) => d }, + archived: { refersto: 'archived', dataval: (d) => d }, + + avatar: { refersto: 'avatar', dataval: (d) => d }, + img: { refersto: 'avatar', dataval: (d) => `` }, + imgsrc: { refersto: 'avatar', dataval: (d) => d }, + imgsrc_short: { refersto: 'avatar', dataval: (d) => d.slice(0, Math.max(d.indexOf(`?`), 0) || d.length) }, + + controlledby: { refersto: 'controlledby', dataval: (d) => d }, + controlledby_name: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + controlledby_names: { refersto: 'controlledby', dataval: (d) => getObjName(d, 'playerlist') }, + player: { refersto: 'controlledby', dataval: (d) => d.split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all' && getObj('player', a))[0] }, + player_name: { refersto: 'controlledby', dataval: (d) => d.split(/\s*,\s*/).filter(a => a.toLowerCase() !== 'all').map(a => getObjName(a, 'player')).filter(a => a)[0] }, + + inplayerjournals: { refersto: 'inplayerjournals', dataval: (d) => d }, + inplayerjournals_name: { refersto: 'inplayerjournals', dataval: (d) => getObjName(d, 'playerlist') }, + inplayerjournals_names: { refersto: 'inplayerjournals', dataval: (d) => getObjName(d, 'playerlist') }, + tags: { refersto: 'tags', dataval: (d) => JSON.parse(d).join(',') } + }; + const tableProps = { + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + name: { refersto: 'name', dataval: (d) => d }, + showplayers: { refersto: 'showplayers', dataval: (d) => d }, + totalweight: { refersto: 'totalweight', dataval: (d) => d } + }; + const tableItemProps = { + id: { refersto: '_id', dataval: (d) => d }, + type: { refersto: '_type', dataval: (d) => d }, + name: { refersto: 'name', dataval: (d) => d }, + + avatar: { refersto: 'avatar', dataval: (d) => d }, + img: { refersto: 'avatar', dataval: (d) => `` }, + imgsrc: { refersto: 'avatar', dataval: (d) => d }, + imgsrc_short: { refersto: 'avatar', dataval: (d) => d.slice(0, Math.max(d.indexOf(`?`), 0) || d.length) }, + + weight: { refersto: 'weight', dataval: (d) => d } + }; + const deckProps = { + + }; + + const condensereturn = (funcret, status, notes) => { + funcret.runloop = (status.includes('changed') || status.includes('unresolved')); + if (status.length) { + funcret.status = status.reduce((m, v) => { + switch (m) { + case 'unchanged': + m = v; + break; + case 'changed': + m = v === 'unresolved' ? v : m; + break; + case 'unresolved': + break; + } + return m; + }); + } + funcret.notes = notes.join('
'); + return funcret; + }; + + const trackerrx = /^tracker(\[(?[^\]]+)]){0,1}((?\+|-)(?\d+)){0,1}$/i; + const rptgitemrx = /(?(?:\*))\((?[^|.]+?)[|.](?
[^\s.|]+?)[|.](?:\[\s*(?.+?)\s*]|(?\$\d+|[a-zA-Z0-9_-]{20}))\s*[|.](?[^[\s).]+?)(?:[|.](?[^\s.[)]+?)){0,1}(?:\[(?[^\]]*?)]){0,1}\s*\)/gi; + //const rptgitemrx = /(?(?:\*))\((?[^|.]+?)[|.](?
[^\s.|]+?)[|.]\[\s*(?.+?)\s*]\s*[|.](?[^[\s).]+?)(?:[|.](?[^\s.[)]+?)){0,1}(?:\[(?[^\]]*?)]){0,1}\s*\)/gi; + const macrorx = /#\((?[^\s.[)]+?)(?:\[(?[^\]]*?)]){0,1}\s*\)/gi; + const multirx = /(?(?:@|%))\((?tracker(?:\[[^\]]+]){0,1}(?:(?:\+|-)\d+){0,1}|[^@*%#|.]+?)[|.](?[^@*%#\s.[|]+?)(?:[|.](?[^@*%#.|[]+?)(?:[|.](?[^[@*%#]+?)){0,1}){0,1}(?:\[(?[^@*%#\]]*?)]){0,1}\s*\)/gi; + + const testConstructs = c => { + return [multirx, rptgitemrx, macrorx].reduce((m, r) => { + m = m || r.test(c); + r.lastIndex = 0; + return m; + }, false); + }; + const simpleObj = (o) => { + if (typeof o === 'undefined') { return o; } + let obj = JSON.parse(JSON.stringify(o)); + if (!Object.keys(obj).length) { return obj; } + return Object.keys(obj).reduce((m, k) => { + if (/^_/.test(k) && !m.hasOwnProperty(k.slice(1))) { m[k.slice(1)] = m[k]; } + return m; + }, obj); + }; + + const handleInput = (msg, msgstate = {}) => { + let funcret = { runloop: false, status: 'unchanged', notes: '' }; + if (msg.type !== 'api' || !testConstructs(msg.content)) return funcret; + if (!Object.keys(msgstate).length && scriptisplugin) return funcret; + let status = []; + let notes = []; + let msgId = generateUUID(); + + const filterObj = { + 'page': (t) => t._pageid === getPageForPlayer(msg.playerid), + 'ribbon': (t) => t._pageid === Campaign().get('playerpageid'), + 'gm': () => true + }; + const propContainers = { + token: tokenProps, + character: charProps, + page: pageProps, + campaign: campaignProps, + marker: markerProps, + player: playerProps, + status: statusProps, + text: textProps, + path: pathProps, + tag: tagProps, + card: cardProps, + handout: handoutProps, + rollabletable: tableProps, + tableitem: tableItemProps + }; + const getPropertyValue = (source, objtype, item, def = '') => { + let propObj = propContainers[objtype.toLowerCase()]; + let retval = def; + if (!source) { + notes.push(`Unable to find a source for property named ${item}. Using default value.`); + } else if (!Object.keys(propObj).includes(item.toLowerCase())) { + notes.push(`Unable to find a ${objtype.toLowerCase()} property named ${item}. Using default value.`); + } else { + retval = propObj[item.toLowerCase()].dataval(source[propObj[item.toLowerCase()].refersto],source); + if (typeof retval === 'undefined') { + notes.push(`Unable to find ${objtype.toLowerCase()} value for ${item}. Using default value.`); + retval = def; + } + } + return retval; + }; + const getCharacterAttribute = (source, type, prop, valtype, def = '') => { + let retval = def; + if (!source) { + notes.push(`Unable to find a character with the given criteria. Using default value.`); + } else { + retval = getSheetItemVal({ groups: { type: type, character: source.id, item: prop, valtype: valtype } }, msg.playerid, source); + if (typeof retval === 'undefined') { + notes.push(`Unable to find ${type === '@' ? 'attribute' : 'ability'} named ${prop} for ${source.name}. Using default value.`); + retval = def; + } + } + return retval; + }; + while (testConstructs(msg.content)) { + msg.content = msg.content.replace(multirx, (m, type, obj, prop, identikey, subprop, def = '') => { + let offset = 0, + trackres, + pgfilter = 'page', + selsource, + presource, + source, + retval = def, + reverse = false, + to; + if (trackerrx.test(obj)) { // if it is a tracker call, it could have an offset, so we detect that first + trackres = trackerrx.exec(obj); + offset = parseInt(trackres.groups.offset || '0'); + if (trackres.groups.operator === '-') reverse = true; + if (playerIsGM(msg.playerid)) pgfilter = trackres.groups.filter || 'page'; + obj = `tracker`; + trackres.lastIndex = 0; + } + if (obj.toLowerCase() === 'speaker') { // if it's a speaker call, determine if player or character, and adjust appropriately + presource = simpleObj(getChar(msg.who, msg.playerid)); + if (presource && presource.name) { + obj = presource.name; + } else { + presource = simpleObj(getPlayer(msg.who)); + if (presource && presource._displayname) { + obj = 'player'; + subprop = identikey; + identikey = prop; + prop = presource._displayname; + } else { + notes.push(`Unable to find the speaker`); + } + } + } + switch (obj.toLowerCase()) { + case 'player': + source = getPlayer(prop); + retval = getPropertyValue(source, obj, identikey, def); + break; + case 'page': + source = simpleObj(getPage(prop)); + retval = getPropertyValue(source, obj, identikey, def); + break; + case 'marker': + source = simpleObj(getMarker(prop)); + retval = getPropertyValue(source, obj, (identikey || 'html'), def); + break; + case 'card': + source = simpleObj(getCard(prop)); + retval = getPropertyValue(source, obj, identikey, def); + break; + case 'campaign': + source = getCampaign(); + retval = getPropertyValue(source, obj, prop, def); + break; + case 'handout': + presource = simpleObj(getHandout(prop) || {}); + if (identikey.toLowerCase() === 'is') { // handout with is + if (subprop) { + source = getTag(presource._id, 'handout', subprop, msg.playerid); + retval = getPropertyValue(source, 'tag', identikey, def); + } + } else { + source = presource; + retval = getPropertyValue(source, obj, identikey, def); + } + break; + case 'table': + presource = simpleObj(getTable(prop, msg.playerid)); + if (presource && presource.id) { + if (presource.hasOwnProperty(identikey)) { + retval = getPropertyValue(presource, 'rollabletable', identikey, def); + } else { + source = simpleObj(getTableItems(identikey, presource)); + retval = getPropertyValue(source, 'tableitem', (subprop || 'name'), def); + } + } + break; + case 'selected': + if (!msg.selected) { // selected but no token => default + notes.push(`No token selected for ${m}. Using default value.`); + retval = def; + } else { + selsource = simpleObj(findObjs({ id: msg.selected[0]._id })[0]); + if (['text', 'path'].includes(selsource.type)) { // text objects and paths + retval = getPropertyValue(selsource, selsource.type, prop, def); + } else { // graphics/tokens/cards + if (Object.keys(tokenProps).includes(prop.toLowerCase())) { // selected with token prop + source = simpleObj(getToken(msg.selected[0]._id)); + retval = getPropertyValue(source, 'token', prop, def); + } else if (prop.toLowerCase() === 'status') { // selected with status + if (identikey && + (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name && + Object.keys(statusProps).includes((subprop || 'value').toLowerCase()) + ) { + presource = simpleObj(getToken(msg.selected[0]._id, getPageID(msg.playerid))); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) {// eslint-disable-line no-prototype-builtins + tokenStatuses[presource.id] = new StatusBlock({ token: presource, msgId: msgId }); + } + source = getStatus(msg.selected[0]._id, getPageID(msg.playerid), identikey, msgId); + retval = getPropertyValue(source, prop, (subprop || 'value'), def); + } + }/* else if (prop.toLowerCase() === 'tagged') { // selected with tagged + if (identikey && Object.keys(tagProps).includes((subprop || 'is').toLowerCase())) { + presource = simpleObj(getToken(msg.selected[0]._id)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (presource.represents) { + source = getTag(presource.id, 'character', identikey, msg.playerid); + retval = getPropertyValue(source, 'tag', (subprop || 'is'), def); + } + } + }*/ else if (prop.toLowerCase() === 'is') { // selected with is + if (identikey) { + if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name) { // is with status + presource = simpleObj(getToken(msg.selected[0]._id, getPageID(msg.playerid))); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) {// eslint-disable-line no-prototype-builtins + tokenStatuses[presource.id] = new StatusBlock({ token: presource, msgId: msgId }); + } + source = getStatus(msg.selected[0]._id, getPageID(msg.playerid), identikey, msgId); + retval = getPropertyValue(source, 'status', 'is', def); + } else { // is with tag + presource = simpleObj(getToken(msg.selected[0]._id)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (presource.represents) { + source = getTag(presource.id, 'character', identikey, msg.playerid); + retval = getPropertyValue(source, 'tag', 'is', def); + } + } + } + } else { // selected with character prop/attribute/ability + source = simpleObj(getChar(msg.selected[0]._id, msg.playerid)); + if (Object.keys(charProps).includes(prop.toLowerCase())) { // selected + character prop + retval = getPropertyValue(source, 'character', prop, def); + } else { // selected + character attribute or ability + retval = getCharacterAttribute(source, type, prop, identikey, def); + } + } + } + } + + break; + case 'tracker': + to = JSON.parse(Campaign().get('turnorder') || '[]').filter(filterObj[pgfilter] || filterObj['page']); + if (!to.length || to[0].id === '-1') { + notes.push(`No tracker token for ${m}. Using default value.`); + retval = def; + } else { + presource = to[(reverse ? to.length - (offset % to.length) : offset % to.length) % to.length]; + if (Object.keys(tokenProps).includes(prop.toLowerCase())) { // tracker + token property + source = simpleObj(getToken(presource.id, presource._pageid)); + retval = getPropertyValue(source, 'token', prop, def); + } else if (prop.toLowerCase() === 'status') { // tracker with status + if (identikey && + (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name && + Object.keys(statusProps).includes((subprop || 'value').toLowerCase())) { + presource = simpleObj(getToken(presource.id, presource._pageid)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) { + tokenStatuses[presource.id] = new StatusBlock({ token: presource, msgId: msgId }); + } + source = getStatus(presource.id, getPageID(msg.playerid), identikey, msgId); + retval = getPropertyValue(source, prop, (subprop || 'value'), def); + } + }/* else if (prop.toLowerCase() === 'tagged') { // tracker with tagged + if (identikey && Object.keys(tagProps).includes((subprop || 'is').toLowerCase())) { + presource = simpleObj(getToken(presource.id, presource._pageid)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (presource.represents) { + source = getTag(presource.id, 'character', identikey, msg.playerid); + retval = getPropertyValue(source, 'tag', (subprop || 'is'), def); + } + } + }*/ else if (prop.toLowerCase() === 'is') { // tracker with is + if (identikey) { + if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name) { // is with status + presource = simpleObj(getToken(presource.id, presource._pageid)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) { + tokenStatuses[presource.id] = new StatusBlock({ token: presource, msgId: msgId }); + } + source = getStatus(msg.selected[0]._id, getPageID(msg.playerid), identikey, msgId); + retval = getPropertyValue(source, 'status', 'is', def); + } else { // is with tag + presource = simpleObj(getToken(presource.id, presource._pageid)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (presource.represents) { + source = getTag(presource.id, 'character', identikey, msg.playerid); + retval = getPropertyValue(source, 'tag', 'is', def); + } + } + } + } else { // tracker with character prop/attribute/ability + source = simpleObj(getChar(presource.id, msg.playerid)); + if (Object.keys(charProps).includes(prop.toLowerCase())) { // tracker + character prop + retval = getPropertyValue(simpleObj(source), 'character', prop, def); + } else { //tracker + character attribute/ability + retval = getCharacterAttribute(source, type, prop, identikey, def); + } + } + } + break; + default: // all others -- could be token name, token id, character name, or character id + selsource = simpleObj(findObjs({ id: obj })[0] || {}); + if (selsource.type && ['text', 'path'].includes(selsource.type)) { // text objects and paths + retval = getPropertyValue(selsource, selsource.type, prop, def); + } else { // graphics/tokens/cards/status/character + if (Object.keys(tokenProps).includes(prop.toLowerCase())) { // token property + source = simpleObj(getToken(obj, getPageID(msg.playerid))); + retval = getPropertyValue(source, 'token', prop, def); + } else if (prop.toLowerCase() === 'status') { // status + if (identikey && + (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name && + Object.keys(statusProps).includes((subprop || 'value').toLowerCase())) { + presource = simpleObj(getToken(obj, getPageID(msg.playerid))); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) { + tokenStatuses[presource.id] = new StatusBlock({ token: presource, msgId: msgId }); + } + source = getStatus(obj, getPageID(msg.playerid), identikey, msgId); + retval = getPropertyValue(source, prop, (subprop || 'value'), def); + } + }/* else if (prop.toLowerCase() === 'tagged') { // tagged + if (identikey && Object.keys(tagProps).includes((subprop || 'is').toLowerCase())) { + presource = simpleObj(getChar(obj, msg.playerid) || getChar((simpleObj(getToken(obj, getPageID(msg.playerid))) || {}).represents, msg.playerid)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (presource) { + source = getTag(presource.id, 'character', identikey, msg.playerid); + retval = getPropertyValue(source, 'tag', (subprop || 'is'), def); + } + } + }*/ else if (prop.toLowerCase() === 'is') { // is + if (identikey) { + if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name) { // is with status + presource = simpleObj(getToken(obj, getPageID(msg.playerid))); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) {// eslint-disable-line no-prototype-builtins + tokenStatuses[presource.id] = new StatusBlock({ token: presource, msgId: msgId }); + } + source = getStatus(obj, getPageID(msg.playerid), identikey, msgId); + retval = getPropertyValue(source, 'status', 'is', def); + } else { // is with tag + presource = simpleObj(getChar(obj, msg.playerid) || getChar((simpleObj(getToken(obj, getPageID(msg.playerid))) || {}).represents, msg.playerid)); + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + if (presource) { + source = getTag(presource.id, 'character', identikey, msg.playerid); + retval = getPropertyValue(source, 'tag', 'is', def); + } + } + } + } else { // character property or attribute or ability + source = simpleObj(getChar(obj, msg.playerid) || getChar((simpleObj(getToken(obj, getPageID(msg.playerid))) || {}).represents, msg.playerid)); + if (Object.keys(charProps).includes(prop.toLowerCase())) { // character property + retval = getPropertyValue(simpleObj(source), 'character', prop, def); + } else { // character attribute/ability + retval = getCharacterAttribute(source, type, prop, identikey, def); + } + } + } + break; + } + status.push('changed'); + return retval; + }); + + // REPEATING SHEET ITEMS + msg.content = msg.content.replace(rptgitemrx, (m, type, obj, section, pattern, reference, valuesuffix, valtype, def = '') => { + let bsel = false; + let offset = 0, + trackres, + pgfilter = 'page', + presource, + source, + retval, + reverse = false, + to; + if (trackerrx.test(obj)) { // if it is a tracker call, it could have an offset, so we detect that first + trackres = trackerrx.exec(obj); + offset = parseInt(trackres.groups.offset || '0'); + if (trackres.groups.operator === '-') reverse = true; + if (playerIsGM(msg.playerid)) pgfilter = trackres.groups.filter || 'page'; + obj = `tracker`; + trackres.lastIndex = 0; + } + switch (obj.toLowerCase()) { + case 'selected': + if (!msg.selected) { + notes.push(`No token selected for ${m}. Using default value.`); + bsel = true; + retval = def; + } else { + source = getChar(msg.selected[0]._id, msg.playerid); + } + break; + case 'speaker': + source = getChar(msg.who, msg.playerid); + break; + case 'tracker': + to = JSON.parse(Campaign().get('turnorder') || '[]').filter(filterObj[pgfilter] || filterObj['page']); + if (!to.length || to[0].id === '-1') { + notes.push(`No tracker token for ${m}. Using default value.`); + retval = def; + } else { + presource = to[(reverse ? to.length - (offset % to.length) : offset % to.length) % to.length]; + if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; + source = simpleObj(getChar(presource.id, msg.playerid)); + } + break; + default: + source = getChar(obj, msg.playerid); + } + + if (!source) { + if (!bsel) notes.push(`Unable to find character for ${m}. Using default value.`); //track note only if we haven't already tracked no selected + retval = def; + } else { + retval = getSheetItemVal({ groups: { type: type, character: source.id, section: section, pattern: pattern, reference: reference, valuesuffix: valuesuffix, valtype: valtype } }, msg.playerid, source); + if (typeof retval === 'undefined') { + notes.push(`Unable to find repeating item for ${m}. Using default value.`); + retval = def; + } + } + if (retval) status.push('changed'); + return retval; + }); + + // MACROS + msg.content = msg.content.replace(macrorx, (m, item, def = '') => { + let retval = def; + let locobj = findObjs({ type: 'macro', name: item })[0]; + const validator = e => ['all', msg.playerid].includes(e); + if (!locobj || !(msg.playerid === locobj.get('_playerid') || locobj.get('visibleto').split(',').some(validator))) { + status.push('unresolved'); + notes.push(`Unable to find macro named ${item}. Using default value.`); + return retval; + } + retval = locobj.get('action') || ''; + status.push('changed'); + return retval; + }); + } + return condensereturn(funcret, status, notes); + }; + + const checkDependencies = (deps) => { + /* pass array of objects like + { name: 'ModName', version: '#.#.#' || '', mod: ModName || undefined, checks: [ [ExposedItem, type], [ExposedItem, type] ] } + */ + const dependencyEngine = (deps) => { + const versionCheck = (mv, rv) => { + let modv = [...mv.split('.'), ...Array(4).fill(0)].slice(0, 4); + let reqv = [...rv.split('.'), ...Array(4).fill(0)].slice(0, 4); + return reqv.reduce((m, v, i) => { + if (m.pass || m.fail) return m; + if (i < 3) { + if (parseInt(modv[i]) > parseInt(reqv[i])) m.pass = true; + else if (parseInt(modv[i]) < parseInt(reqv[i])) m.fail = true; + } else { + // all betas are considered below the release they are attached to + if (reqv[i] === 0 && modv[i] === 0) m.pass = true; + else if (modv[i] === 0) m.pass = true; + else if (reqv[i] === 0) m.fail = true; + else if (parseInt(modv[i].slice(1)) >= parseInt(reqv[i].slice(1))) m.pass = true; + } + return m; + }, { pass: false, fail: false }).pass; + }; + + let result = { passed: true, failures: {}, optfailures: {} }; + deps.forEach(d => { + let failObj = d.optional ? result.optfailures : result.failures; + if (!d.mod) { + if (!d.optional) result.passed = false; + failObj[d.name] = 'Not found'; + return; + } + if (d.version && d.version.length) { + if (!(API_Meta[d.name].version && API_Meta[d.name].version.length && versionCheck(API_Meta[d.name].version, d.version))) { + if (!d.optional) result.passed = false; + failObj[d.name] = `Incorrect version. Required v${d.version}. ${API_Meta[d.name].version && API_Meta[d.name].version.length ? `Found v${API_Meta[d.name].version}` : 'Unable to tell version of current.'}`; + return; + } + } + d.checks.reduce((m, c) => { + if (!m.passed) return m; + let [pname, ptype] = c; + if (!d.mod.hasOwnProperty(pname) || typeof d.mod[pname] !== ptype) { + if (!d.optional) m.passed = false; + failObj[d.name] = `Incorrect version.`; + } + return m; + }, result); + }); + return result; + }; + let depCheck = dependencyEngine(deps); + let failures = '', contents = '', msg = ''; + if (Object.keys(depCheck.optfailures).length) { // optional components were missing + failures = Object.keys(depCheck.optfailures).map(k => `• ${k} : ${depCheck.optfailures[k]}`).join('
'); + contents = `${apiproject} utilizies one or more other scripts for optional features, and works best with those scripts installed. You can typically find these optional scripts in the 1-click Mod Library:
${failures}`; + msg = `
MISSING MOD DETECTED
${contents}
`; + sendChat(apiproject, `/w gm ${msg}`); + } + if (!depCheck.passed) { + failures = Object.keys(depCheck.failures).map(k => `• ${k} : ${depCheck.failures[k]}`).join('
'); + contents = `${apiproject} requires other scripts to work. Please use the 1-click Mod Library to correct the listed problems:
${failures}`; + msg = `
MISSING MOD DETECTED
${contents}
`; + sendChat(apiproject, `/w gm ${msg}`); + return false; + } + return true; + }; + + + let scriptisplugin = false; + // const fetch = async (m, s) => await handleInput(m, s); + const fetch = (m, s) => handleInput(m, s); + on('chat:message', handleInput); + on('ready', () => { + versionInfo(); + logsig(); + + let reqs = [ + { + name: 'checkLightLevel', +// version: `1.0.0.b3`, + mod: typeof checkLightLevel !== 'undefined' ? checkLightLevel : undefined, + checks: [['isLitBy', 'function']], + optional: true + }, + { + name: 'libTokenMarkers', + version: `0.1.2`, + mod: typeof libTokenMarkers !== 'undefined' ? libTokenMarkers : undefined, + checks: [['getStatus', 'function'], ['getStatuses', 'function'], ['getOrderedList', 'function']] + }, + { + name: 'Messenger', + version: `1.0.0`, + mod: typeof Messenger !== 'undefined' ? Messenger : undefined, + checks: [['Button', 'function'], ['MsgBox', 'function'], ['HE', 'function'], ['Html', 'function'], ['Css', 'function']] + } + ]; + if (!checkDependencies(reqs)) return; + html = Messenger.Html(); + css = Messenger.Css(); + HE = Messenger.HE; + + on('chat:message', handleConfig); + + scriptisplugin = (typeof ZeroFrame !== `undefined`); + if (typeof ZeroFrame !== 'undefined') { + ZeroFrame.RegisterMetaOp(fetch); + } + }); + return { + }; +})(); +{ try { throw new Error(''); } catch (e) { API_Meta.Fetch.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Fetch.offset); } } +/* */ diff --git a/Fetch/Fetch.js b/Fetch/Fetch.js index aff93579d2..d3342b3d33 100644 --- a/Fetch/Fetch.js +++ b/Fetch/Fetch.js @@ -3,8 +3,8 @@ Name : Fetch GitHub : https://github.com/TimRohr22/Cauldron/tree/master/Fetch Roll20 Contact : timmaugh -Version : 2.1.1 -Last Update : 14 JUN 2024 +Version : 2.1.2 +Last Update : 6 OCT 2025 ========================================================= */ var API_Meta = API_Meta || {}; @@ -13,12 +13,12 @@ API_Meta.Fetch = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; const Fetch = (() => { //eslint-disable-line no-unused-vars const apiproject = 'Fetch'; - const version = '2.1.1'; + const version = '2.1.2'; const apilogo = 'https://i.imgur.com/jeIkjvS.png'; const apilogoalt = 'https://i.imgur.com/boYO3cf.png'; const schemaVersion = 0.2; API_Meta[apiproject].version = version; - const vd = new Date(1718394151411); + const vd = new Date(1759763868181); const versionInfo = () => { log(`\u0166\u0166 ${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} \u0166\u0166 -- offset ${API_Meta[apiproject].offset}`); if (!state.hasOwnProperty(apiproject) || state[apiproject].version !== schemaVersion) { //eslint-disable-line no-prototype-builtins @@ -530,8 +530,13 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars this.token = token; this.msgId = msgId; this.statuses = (decomposeStatuses(token.statusmarkers) || []).reduce((m, s) => { - m[s.name] = m[s.name] || [] + m[s.name] = m[s.name] || []; m[s.name].push(Object.assign({}, s, { is: 'yes' })); + let shortTag = s.tag.split(/::/)[0]; + if (shortTag !== s.name) { + m[shortTag] = m[shortTag] || []; + m[shortTag].push(Object.assign({}, s, { is: 'yes' })); + } return m; }, {}); } @@ -791,6 +796,15 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars bar3: { refersto: 'bar3_value', dataval: (d) => d }, bar3_current: { refersto: 'bar3_value', dataval: (d) => d }, bar3_value: { refersto: 'bar3_value', dataval: (d) => d }, + bar4_link: { refersto: 'bar4_link', dataval: (d) => d }, + link4: { refersto: 'bar4_link', dataval: (d) => d }, + bar4_name: { refersto: 'bar4_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + name4: { refersto: 'bar4_link', dataval: d => /^sheetattr_/.test(d) ? d.replace(/^sheetattr_/, '') : getObjName(d, 'attribute') }, + bar4_max: { refersto: 'bar4_max', dataval: (d) => d }, + max4: { refersto: 'bar4_max', dataval: (d) => d }, + bar4: { refersto: 'bar4_value', dataval: (d) => d }, + bar4_current: { refersto: 'bar4_value', dataval: (d) => d }, + bar4_value: { refersto: 'bar4_value', dataval: (d) => d }, bright_light_distance: { refersto: 'bright_light_distance', dataval: (d) => d }, cardid: { refersto: '_cardid', dataval: (d) => d }, cid: { refersto: '_cardid', dataval: (d) => d }, @@ -1020,6 +1034,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars turnorder: { refersto: 'turnorder', dataval: (d) => d }, initiativepage: { refersto: 'initiativepage', dataval: (d) => d }, nodeversion: { refersto: 'nodeversion', dataval: (d) => d }, + sandboxversion: { refersto: 'sandboxVersion', dataval: (d) => d }, pageid: { refersto: 'playerpageid', dataval: (d) => d }, page_id: { refersto: 'playerpageid', dataval: (d) => d }, playerpageid: { refersto: 'playerpageid', dataval: (d) => d }, @@ -1373,8 +1388,9 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars retval = getPropertyValue(source, 'token', prop, def); } else if (prop.toLowerCase() === 'status') { // selected with status if (identikey && - (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey.toLowerCase())[1]) || {}).name && - Object.keys(statusProps).includes((subprop || 'value').toLowerCase())) { + (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name && + Object.keys(statusProps).includes((subprop || 'value').toLowerCase()) + ) { presource = simpleObj(getToken(msg.selected[0]._id, getPageID(msg.playerid))); if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) {// eslint-disable-line no-prototype-builtins @@ -1394,7 +1410,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars } }*/ else if (prop.toLowerCase() === 'is') { // selected with is if (identikey) { - if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey.toLowerCase())[1]) || {}).name) { // is with status + if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name) { // is with status presource = simpleObj(getToken(msg.selected[0]._id, getPageID(msg.playerid))); if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) {// eslint-disable-line no-prototype-builtins @@ -1435,7 +1451,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars retval = getPropertyValue(source, 'token', prop, def); } else if (prop.toLowerCase() === 'status') { // tracker with status if (identikey && - (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey.toLowerCase())[1]) || {}).name && + (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name && Object.keys(statusProps).includes((subprop || 'value').toLowerCase())) { presource = simpleObj(getToken(presource.id, presource._pageid)); if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; @@ -1456,7 +1472,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars } }*/ else if (prop.toLowerCase() === 'is') { // tracker with is if (identikey) { - if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey.toLowerCase())[1]) || {}).name) { // is with status + if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name) { // is with status presource = simpleObj(getToken(presource.id, presource._pageid)); if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) { @@ -1493,7 +1509,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars retval = getPropertyValue(source, 'token', prop, def); } else if (prop.toLowerCase() === 'status') { // status if (identikey && - (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey.toLowerCase())[1]) || {}).name && + (getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name && Object.keys(statusProps).includes((subprop || 'value').toLowerCase())) { presource = simpleObj(getToken(obj, getPageID(msg.playerid))); if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; @@ -1514,7 +1530,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars } }*/ else if (prop.toLowerCase() === 'is') { // is if (identikey) { - if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey.toLowerCase())[1]) || {}).name) { // is with status + if ((getMarker(/(?.+?)(?:\?(?\d+|all\+?))?$/.exec(identikey)[1]) || {}).name) { // is with status presource = simpleObj(getToken(obj, getPageID(msg.playerid))); if (presource && !presource.hasOwnProperty('id')) presource.id = presource._id; if (!tokenStatuses.hasOwnProperty(presource.id) || tokenStatuses[presource.id].msgId !== msgId) {// eslint-disable-line no-prototype-builtins @@ -1696,6 +1712,7 @@ const Fetch = (() => { //eslint-disable-line no-unused-vars let scriptisplugin = false; + // const fetch = async (m, s) => await handleInput(m, s); const fetch = (m, s) => handleInput(m, s); on('chat:message', handleInput); on('ready', () => { diff --git a/Fetch/script.json b/Fetch/script.json index 8aeb6469fc..8d655750c7 100644 --- a/Fetch/script.json +++ b/Fetch/script.json @@ -1,7 +1,7 @@ { "name": "Fetch", "script": "Fetch.js", - "version": "2.1.1", + "version": "2.1.2", "description": "Fetch is a meta-script and part of the Meta-Toolbox. Fetch offers a unified syntax to expand the amount of things that can be retrieved with simple token or sheet calls. You can retrieve any token property, character property, sheet attribute, repeating attribute, ability, or macro with syntax that is intentionally very similar to Roll20 Galactic Standard syntax structures. As of v2.0.0, you can also retrieve page properties, player properties, campaign properties, token marker properties, or statusmarker properties.\r\rToken property : @(selected.currentside)\rSheet Attribute: @(selected.Strength)\rSheet Attribute: @(Bob the Hirsute.Strength.max)\rRepeating Attr : *(Englebert Slaptiback.spells.[spell_name~Fireball prepared].spell_roll)\r\rIt also expands the source of the returned sheet item to include 'speaker'.\r\r@(speaker.Strength.max) ... and can return the rowID of a repeating attribute, the row number ($0), or the name of either brand of reference.\r\rNot only do these offer the advantage of not breaking the chat message if they don't exist (the way a standard token or sheet item call would), they also give you the ability to substitute in a default value should the one you are looking for not exist: \r\r@(The President of Burundi.Coffee[default value here]) \r\rFor more information, see the original API forum thread:\r\rhttps://app.roll20.net/forum/post/10005732/meta-script-fetch-retrieve-attributes-repeating-attributes-abilities-or-token-properties)\r\rOr read about the full set of meta-scripts available: \r\r[Meta Toolbox Forum Thread](https://app.roll20.net/forum/post/10005695/script-set-the-meta-toolbox)", "authors": "timmaugh", "roll20userid": "5962076", @@ -36,7 +36,8 @@ "2.0.7", "2.0.8", "2.0.9", - "2.1.0" + "2.1.0", + "2.1.1" ] } \ No newline at end of file diff --git a/SelectManager/1.1.10/SelectManager.js b/SelectManager/1.1.10/SelectManager.js new file mode 100644 index 0000000000..4e7f5672ec --- /dev/null +++ b/SelectManager/1.1.10/SelectManager.js @@ -0,0 +1,1140 @@ +/* +========================================================= +Name : SelectManager +GitHub : https://github.com/TimRohr22/Cauldron/tree/master/SelectManager +Roll20 Contact : timmaugh && TheAaron +Version : 1.1.10 +Last Update : 6 OCT 2025 +========================================================= +*/ +var API_Meta = API_Meta || {}; +API_Meta.SelectManager = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; +{ try { throw new Error(''); } catch (e) { API_Meta.SelectManager.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (12)); } } + +const SelectManager = (() => { //eslint-disable-line no-unused-vars + // ================================================== + // VERSION + // ================================================== + const apiproject = 'SelectManager'; + const version = '1.1.10'; + const schemaVersion = 0.4; + const apilogo = 'https://i.imgur.com/ewyOzMU.png'; + const apilogoalt = 'https://i.imgur.com/3U8c9rE.png' + API_Meta[apiproject].version = version; + const vd = new Date(1759763868181); + const versionInfo = () => { + log(`\u0166\u0166 ${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} \u0166\u0166 -- offset ${API_Meta[apiproject].offset}`); + if (!state.hasOwnProperty(apiproject) || state[apiproject].version !== schemaVersion) { + log(` > Updating ${apiproject} Schema to v${schemaVersion} <`); + switch (state[apiproject] && state[apiproject].version) { + + case 0.1: + state[apiproject].settings = { + playerscanids: false + }; + if (state[apiproject].hasOwnProperty('autoinsert')) state[apiproject].settings.autoinsert = [...state[apiproject].autoinsert]; + else state[apiproject].settings.autoinsert = ['selected']; + state[apiproject].defaults = { + autoinsert: ['selected'], + playerscanids: false + }; + delete state[apiproject].autoinsert; + /* falls through */ + case 0.2: + state[apiproject].settings.knownsenders = ['CRL']; + state[apiproject].defaults.knownsenders = ['CRL']; + /* falls through */ + case 0.3: + state[apiproject].settings.show04message = true; + state[apiproject].defaults.show04message = true; + /* falls through */ + case 'UpdateSchemaVersion': + state[apiproject].version = schemaVersion; + break; + + default: + state[apiproject] = { + version: schemaVersion, + settings: { + autoinsert: ['selected'], + playerscanids: false, + knownsenders: ['CRL'], + show03message: true + }, + defaults: { + autoinsert: ['selected'], + playerscanids: false, + knownsenders: ['CRL'], + show03message: true + } + }; + break; + } + } + }; + const manageState = { // eslint-disable-line no-unused-vars + reset: () => state[apiproject].settings = _.clone(state[apiproject].defaults), + set: (p, v) => state[apiproject].settings[p] = v, + get: (p) => { return state[apiproject].settings[p]; } + }; + + const logsig = () => { + // initialize shared namespace for all signed projects, if needed + state.torii = state.torii || {}; + // initialize siglogged check, if needed + state.torii.siglogged = state.torii.siglogged || false; + state.torii.sigtime = state.torii.sigtime || Date.now() - 3001; + if (!state.torii.siglogged || Date.now() - state.torii.sigtime > 3000) { + const logsig = '\n' + + ' _____________________________________________ ' + '\n' + + ' )_________________________________________( ' + '\n' + + ' )_____________________________________( ' + '\n' + + ' ___| |_______________| |___ ' + '\n' + + ' |___ _______________ ___| ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + ' | | | | ' + '\n' + + '______________|_|_______________|_|_______________' + '\n' + + ' ' + '\n'; + log(`${logsig}`); + state.torii.siglogged = true; + state.torii.sigtime = Date.now(); + } + return; + }; + const generateUUID = (() => { + let a = 0; + let b = []; + + return () => { + let c = (new Date()).getTime() + 0; + let f = 7; + let e = new Array(8); + let d = c === a; + a = c; + for (; 0 <= f; f--) { + e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); + c = Math.floor(c / 64); + } + c = e.join(""); + if (d) { + for (f = 11; 0 <= f && 63 === b[f]; f--) { + b[f] = 0; + } + b[f]++; + } else { + for (f = 0; 12 > f; f++) { + b[f] = Math.floor(64 * Math.random()); + } + } + for (f = 0; 12 > f; f++) { + c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); + } + return c; + }; + })(); + const escapeRegExp = (string) => { return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); }; + const RX = (() => { + const esRE = (s) => s.replace(/(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g, '\\$1'); + const entities = { + '*': { detect: /\*/, rx: /\*/, rep: '.*?' }, + '?': { detect: /\?/, rx: /\?/, rep: '.' }, + // '?': { detect: /.\?/, rx: /(.)\?/, rep: '$1?'} + }; + const rxkeys = (k) => entities[k].detect.source; + const getSource = (s) => { + let rxsource = ''; + let rxflags = ''; + let ret; + const rxpattern = /^\/(?.*?)\/(?(?:g|i|m|s|u|y){0,6})$/i; + if (rxpattern.test(s)) { + ret = rxpattern.exec(s); + rxsource = ret.groups.source; + rxflags = ret.groups.flags || ''; + } else { + rxsource = ['^', + ...s.split(new RegExp(`(${Object.keys(entities).map(rxkeys).join('|')})`)) + .map(p => { + return Object.keys(entities).reduce((m, k) => { + let rx = new RegExp(`^${entities[k].rx.source}$`); + if (typeof m === 'undefined' && rx.test(p)) { + m = p.replace(rx, entities[k].rep); + } + return m; + }, undefined) || esRE(p); + }), + '$' + ].join(''); + rxflags = 'gi'; + } + return new RegExp(rxsource, rxflags); + }; + return getSource; + })(); + const playersCanUseIDs = () => manageState.get('playerscanids'); + const getTheSpeaker = msg => { + let speaking; + if (['API', ''].includes(msg.who)) { + speaking = { id: undefined, type: 'API', localName: 'API', speakerType: 'API', chatSpeaker: 'API', get: () => { return 'API'; } }; + } else { + let characters = findObjs({ type: 'character' }); + characters.forEach(c => { if (c.get('name') === msg.who) speaking = c; }); + + if (speaking) { + speaking.speakerType = "character"; + speaking.localName = speaking.get("name"); + } else { + speaking = getObj('player', msg.playerid); + speaking.speakerType = "player"; + speaking.localName = speaking.get("displayname"); + } + speaking.chatSpeaker = speaking.speakerType + '|' + speaking.id; + } + + return speaking; + }; + const playerCanControl = (obj, playerid = 'any') => { + const playerInControlledByList = (list, playerid) => list.includes('all') || list.includes(playerid) || ('any' === playerid && list.length); + let players = obj.get('controlledby') + .split(/,/) + .filter(s => s.length); + + if (playerInControlledByList(players, playerid)) { + return true; + } + + if ('' !== obj.get('represents')) { + players = (getObj('character', obj.get('represents')) || { get: function () { return ''; } }) + .get('controlledby').split(/,/) + .filter(s => s.length); + return playerInControlledByList(players, playerid); + } + return false; + }; + const getPageForPlayer = (playerid) => { + let player = getObj('player', playerid); + if (playerIsGM(playerid)) { + return player.get('lastpage') || Campaign().get('playerpageid'); + } + + let psp = Campaign().get('playerspecificpages'); + if (psp[playerid]) { + return psp[playerid]; + } + + return Campaign().get('playerpageid'); + }; + const getTokens = (query, pid, owner = true) => { + if (pid === 'API') pid = preservedMsgObj[maintrigger].playerid; + let pageid = getPageForPlayer(pid); + let qrx = RX(query); + let alltokens = [...findObjs({ type: 'graphic', pageid: pageid }), ...findObjs({ type: 'text', pageid: pageid }), ...findObjs({ type: 'path', pageid: pageid })] + .filter(t => t.get('layer') === 'objects' || playerIsGM(pid)); + if (owner) { + alltokens = alltokens.filter(t => playerIsGM(pid) || playersCanUseIDs() || playerCanControl(t, pid)); + } + let tokens = [(alltokens.filter(t => t.id === query)[0] || + alltokens.filter(t => t.get('name') === query)[0])] + .filter(t => t); + if (!tokens.length) { + tokens = alltokens.filter(t => { + qrx.lastIndex = 0; + return qrx.test(typeof t.get('name') === 'undefined' ? '' : t.get('name')); + }); + } + return tokens; + }; + + let html = {}; + let css = {}; // eslint-disable-line no-unused-vars + let HE = () => { }; // eslint-disable-line no-unused-vars + const theme = { + primaryColor: '#E66B00', + primaryTextColor: '#232323', + primaryTextBackground: '#ededed' + } + const localCSS = { + msgheader: { + 'background-color': theme.primaryColor, + 'color': 'white', + 'font-size': '1.2em', + 'padding-left': '4px' + }, + msgbody: { + 'color': theme.primaryTextColor, + 'background-color': theme.primaryTextBackground + }, + msgfooter: { + 'color': theme.primaryTextColor, + 'background-color': theme.primaryTextBackground + }, + msgheadercontent: { + 'display': 'table-cell', + 'vertical-align': 'middle', + 'padding': '4px 8px 4px 6px' + }, + msgheaderlogodiv: { + 'display': 'table-cell', + 'max-height': '30px', + 'margin-right': '8px', + 'margin-top': '4px', + 'vertical-align': 'middle' + }, + logoimg: { + 'background-color': 'transparent', + 'float': 'left', + 'border': 'none', + 'max-height': '30px' + }, + boundingcss: { + 'background-color': theme.primaryTextBackground + }, + inlineEmphasis: { + 'font-weight': 'bold' + }, + button: { + 'background-color': theme.primaryColor, + 'border-radius': '6px', + 'min-width': '25px', + 'padding': '6px 8px' + } + } + const msgbox = ({ + msg: msg = '', + title: title = '', + headercss: headercss = localCSS.msgheader, + bodycss: bodycss = localCSS.msgbody, + footercss: footercss = localCSS.msgfooter, + sendas: sendas = 'SelectManager', + whisperto: whisperto = '', + footer: footer = '', + btn: btn = '', + } = {}) => { + if (title) title = html.div(html.div(html.img(apilogoalt, 'SelectManager Logo', localCSS.logoimg), localCSS.msgheaderlogodiv) + html.div(title, localCSS.msgheadercontent), {}); + Messenger.MsgBox({ msg: msg, title: title, bodycss: bodycss, sendas: sendas, whisperto: whisperto, footer: footer, btn: btn, headercss: headercss, footercss: footercss, boundingcss: localCSS.boundingcss, noarchive: true }); + }; + + const getWhisperTo = (who) => who.toLowerCase() === 'api' ? 'gm' : who.replace(/\s\(gm\)$/i, ''); + const handleConfig = msg => { + if (msg.type !== 'api' || !/^!smconfig/.test(msg.content)) return; + let recipient = getWhisperTo(msg.who); + if (!playerIsGM(msg.playerid)) { + msgbox({ title: 'GM Rights Required', msg: 'You must be a GM to perform that operation', whisperto: recipient }); + return; + } + let cfgrx = /^(\+|-)(selected|who|playerid|playerscanids|acknowledge(\d+))$/i; + let changeObj = { + '+': 'enabled', + '-': 'disabled', + 'a': 'acknowledged' + }; + let res; + let cfgTrack = {}; + let message; + if (/^!smconfig\s+[^\s]/.test(msg.content)) { + msg.content.split(/\s+/).slice(1).forEach(a => { + res = cfgrx.exec(a); + if (!res) return; + if (res[2].toLowerCase() === 'playerscanids') { + manageState.set('playerscanids', (res[1] === '+')); + cfgTrack[res[2]] = res[1]; + } else if (['selected', 'who', 'playerid'].includes(res[2].toLowerCase())) { + if (res[1] === '+') { + manageState.set('autoinsert', [...new Set([...manageState.get('autoinsert'), res[2].toLowerCase()])]); + cfgTrack[res[2]] = res[1]; + } else { + manageState.set('autoinsert', manageState.get('autoinsert').filter(e => e !== res[2].toLowerCase())); + cfgTrack[res[2]] = res[1]; + } + } else if (/^acknowledge\d+$/i.test(res[2])) { + manageState.set(`show${res[3]}message`, false); + cfgTrack[`Schema ${res[3]} Message`] = 'a'; + } + }); + let changes = Object.keys(cfgTrack).map(k => `${html.span(k, localCSS.inlineEmphasis)}: ${changeObj[cfgTrack[k]]}`).join('
'); + msgbox({ title: `SelectManager Config Changed`, msg: `You have made the following changes to the SelectManager configuration:
${changes}`, whisperto: recipient }); + } else { + cfgTrack.playerscanids = `${html.span('playerscanids', localCSS.inlineEmphasis)}: ${manageState.get('playerscanids') ? 'enabled' : 'disabled'}`; + cfgTrack.autoinsert = ['selected', 'who', 'playerid'].map(k => `${html.span(k, localCSS.inlineEmphasis)}: ${manageState.get('autoinsert').includes(k) ? 'enabled' : 'disabled'}`).join('
'); + message = `SelectManager is currently configured as follows:
${cfgTrack.playerscanids}
${cfgTrack.autoinsert}`; + msgbox({ title: 'SelectManager Configuration', msg: message, whisperto: recipient }); + } + }; + + const issueVersionUpdateMessages = () => { + let allCommands = [...findObjs({ type: 'macro' }), ...findObjs({ type: 'ability' })]; + + const show04Message = () => { + let affected = allCommands.filter(o => { + let cmd = o.get('action'); + let locSelrx = /{&\s*(?:select|inject)\s+([^}]+?)\s*}/gi; + let found = false; + let res; + let items; + while (!found && (res = locSelrx.exec(cmd)) && res) { + found = !!(res[1].split(/\s*,\s*/) + .filter(item => oldmarkerrx.test(item)).length); + // .filter(item => /^(\+|-)/.test(item) && !/^(\+|-)(@.*|#.*|\*.*|((bar|max)(1|2|3){1})|((aura|color)(1|2){0,1})|layer|tip|gmnotes|type|pc|npc|pt|side)(\s|<|>|=|~|!|$)/.test(item)).length); + } + return found; + }); + + if (affected.length) { + let listAffected = affected.map(a => `
  • ${a.get('name')} (${a.get('type') === 'ability' ? `ability for ${getObj('character', a.get('characterid')).get('name')}` : 'macro'})
  • `).join(''); + let message = html.p(`A small portion of SelectManager syntax is changing. A previous update made it possible to use status markers (either their presence or value) as a ` + + `condition for virtually selecting that token. For instance, testing a token for the presence of a status marker named "noble" would look like:

    +noble`) + + html.p(`This syntax allowed for "collisions" -- a situation where a marker might bear the name of one of the other keywords SelectManager looks for as ways to test the tokens: aura, bar1, npc, etc. ` + + `For instance, if you were playing in a game that had a status marker named "npc", then would the syntax +npc refer to the presence of the marker, or to the internal test ` + + `SelectManager uses to determine if a token is an npc?`) + + html.p(`With the v1.1.8 update, SelectManager can now use a similar syntax to test a token for the presence of character tags, increasing the possibility of these collisions (i.e., a tag ` + + `and a marker both named "noble"). Because of this, the syntax to test for a status marker is getting an update to allow for greater specificity. Going forward, ` + + `to test for a status marker on a token, you should simply preface the marker name with an asterisk (*) immediately following the "+" (for "should have") or "-" ` + + `(for "should not have"):

    +*noble
    +*noble > 2`) + + html.p(`The previous syntax is still available for now, but is no longer supported and will be removed at some point in the future. You should take a moment to update commands ` + + `in your game that utilize the previous construction (without an asterisk). A quick scan of character abilities and macros in this game shows that the following list ` + + `might be commands where you have utilized the previous syntax:` + + `
      ${listAffected}
    `); + //const button = ({ elem: elem = '', label: label = '', char: char = '', type: type = '%', css: css = Messenger.Css.button } = {}) => { + + let button = Messenger.Button({ elem: `!smconfig +acknowledge04`, type: '!', label: `Don't Show Again`, css: localCSS.button, noarchive: true }); + msgbox({ title: 'SelectManager Syntax Update', msg: message, whisperto: 'gm', btn: button }); + + // TODO: make sure chat message has opt-out for not getting the message again + } else { + manageState.set('show04message', false); + } + }; + + const messageSettings = { + show04message: show04Message + }; + + Object.keys(messageSettings).forEach(k => { + if (manageState.get(k)) { messageSettings[k](); } + }); + }; + + const maintrigger = `${apiproject}-main`; + let preservedMsgObj = { + [maintrigger]: { selected: undefined, who: '', playerid: '' } + }; + + const condensereturn = (funcret, status, notes) => { + funcret.runloop = (status.includes('changed') || status.includes('unresolved')); + if (status.length) { + funcret.status = status.reduce((m, v) => { + switch (m) { + case 'unchanged': + m = v; + break; + case 'changed': + m = v === 'unresolved' ? v : m; + break; + case 'unresolved': + break; + } + return m; + }); + } + funcret.notes = notes.join('
    '); + return funcret; + }; + const uniqueArrayByProp = (array, prop = 'id') => { + const set = new Set; + return array + .filter(o => typeof o !== 'undefined' && !set.has(o[prop]) && set.add(o[prop])); + }; + let oldmarkerrx; + const decomposeStatuses = (list = '') => { + return list.split(/\s*,\s*/g).filter(s => s.length) + .reduce((m, s) => { + let origst = libTokenMarkers.getStatus(s.slice(0, /(@\d+$|:)/.test(s) ? /(@\d+$|:)/.exec(s).index : s.length)); + let st = _.clone(origst); + if (!st) return m; + st.num = /^.+@0*(\d+)/.test(s) ? /^.+@0*(\d+)/.exec(s)[1] : ''; + st.html = origst.getHTML(); + st.url = st.url || ''; + m.push(st); + return m; + }, []); + }; + class StatusBlock { + constructor({ token: token = {}, msgId: msgId = generateUUID() } = {}) { + this.token = token; + this.msgId = msgId; + this.statuses = (decomposeStatuses(token.get('statusmarkers')) || []).reduce((m, s) => { + m[s.name] = m[s.name] || [] + m[s.name].push(Object.assign({}, s, { is: 'yes' })); + return m; + }, {}); + } + } + + const tokenStatuses = {}; + const getStatus = (token, query, msgId) => { + let rxret, status, index, modindex, statusblock; + if (!token) return; + // token = simpleObj(token); + // if (token && !token.hasOwnProperty('id')) token.id = token._id; + if (!tokenStatuses.hasOwnProperty(token.id) || tokenStatuses[token.id].msgId !== msgId) { + tokenStatuses[token.id] = new StatusBlock({ token: token, msgId: msgId }); + } + rxret = /(?.+?)(?:\?(?\d+|all\+?))?$/.exec(query); + [status, index] = [rxret.groups.marker, rxret.groups.index]; + if (!index) { + modindex = 1; + } else if (['all', 'all+'].includes(index.toLowerCase())) { + modindex = index.toLowerCase(); + } else { + modindex = Number(index); + } + statusblock = tokenStatuses[token.id].statuses[status]; + if (!statusblock || !statusblock.length) { + return { is: 'no', count: '0' }; + }; + switch (index) { + case 'all': + return statusblock.reduce((m, sm) => { + m.num = `${m.num || ''}${sm.num}`; + m.tag = m.tag || sm.tag; + m.url = m.url || sm.url; + m.html = m.html || sm.html; + m.is = 'yes'; + m.count = m.count || statusblock.length; + return m; + }, {}); + case 'all+': + return statusblock.reduce((m, sm) => { + m.num = `${Number(m.num || 0) + Number(sm.num)}`; + m.tag = m.tag || sm.tag; + m.url = m.url || sm.url; + m.html = m.html || sm.html; + m.is = 'yes'; + m.count = m.count || statusblock.length; + return m; + }, {}); + default: + if (statusblock.length >= modindex) { + return Object.assign({}, statusblock[modindex - 1], { count: index ? '1' : statusblock.length }); + } else { + return { is: 'no', 'count': '0' }; + } + } + }; + + const checkTicks = (s, check = ["'", "`", '"']) => { + if (typeof s !== 'string') return s; + return ((s.charAt(0) === s.charAt(s.length - 1)) && check.includes(s.charAt(0))) ? s.slice(1, s.length - 1) : s; + }; + const isPlayerToken = (obj = { get: () => { return undefined; } }, pc = false) => { + let players; + if (!pc) { + players = obj.get('controlledby') + .split(/,/) + .filter(s => s.length); + + if (players.includes('all') || players.filter((p) => !playerIsGM(p)).length) { + return true; + } + } + + if ('' !== obj.get('represents')) { + players = (getObj('character', obj.get('represents')) || { get: function () { return ''; } }) + .get('controlledby') + .split(/,/) + .filter(s => s.length); + return !!(players.includes('all') || players.filter((p) => !playerIsGM(p)).length); + } + return false; + }; + const isNPC = (obj = { get: () => { return ''; } }) => { + let control = ( + obj.get('represents') && obj.get('represents').length + ? getObj('character', obj.get('represents') || { get: function () { return ''; } }) + : obj + ) + .get('controlledby').split(/,/); + if (!control.length) return true; + return !control.filter(s => s.length && !playerIsGM(s)).length; + }; + const internalTestLib = { + 'int': (v) => +v === +v && parseInt(parseFloat(v, 10), 10) == v, + 'num': (v) => +v === +v, + 'tru': (v) => v == true + }; + const typeProcessor = { + '=': (t) => t[0] == t[1], + '!=': (t) => t[0] != t[1], + '~': (t) => t[0].includes(t[1]), + '!~': (t) => !t[0].includes(t[1]), + '>': (t) => (internalTestLib.num(t[0]) ? Number(t[0]) : t[0]) > (internalTestLib.num(t[1]) ? Number(t[1]) : t[1]), + '>=': (t) => (internalTestLib.num(t[0]) ? Number(t[0]) : t[0]) >= (internalTestLib.num(t[1]) ? Number(t[1]) : t[1]), + '<': (t) => (internalTestLib.num(t[0]) ? Number(t[0]) : t[0]) < (internalTestLib.num(t[1]) ? Number(t[1]) : t[1]), + '<=': (t) => (internalTestLib.num(t[0]) ? Number(t[0]) : t[0]) <= (internalTestLib.num(t[1]) ? Number(t[1]) : t[1]), + 'in': (t) => { + let array = (/^\[?([^\]]+)\]?$/.exec(t[1])[1] || '').split(/\s*,\s*/); + return array.includes(t[0]); + } + } + + const evaluateCriteria = (c, t, msgId) => { + let comp = []; + let tksetting; + let test = c.test; + let attrret = 'current'; // current or max + let attrval; + let attrres; + switch (c.type) { + case 'bar': + if (typeProcessor.hasOwnProperty(test)) { + comp = [t.get(`bar${['1', '2', '3', '4'].includes(c.ident) ? c.ident : '1'}_value`), c.value]; + } + break; + case 'max': + if (typeProcessor.hasOwnProperty(test)) { + comp = [t.get(`bar${['1', '2', '3', '4'].includes(c.ident) ? c.ident : '1'}_max`), c.value]; + } + break; + case 'aura': + if (test && test.length && c.value && !isNaN(c.value) && typeProcessor.hasOwnProperty(test)) { // testing radius of aura + tksetting = t.get(`aura${['1', '2'].includes(c.ident) ? c.ident : '1'}_radius`); + if (tksetting && tksetting.length) { + comp = [tksetting, c.value]; + } + } else { // testing presence of aura + tksetting = t.get(`aura${['1', '2'].includes(c.ident) ? c.ident : '1'}_radius`); + comp = [tksetting && tksetting.length > 0, true]; + test = '='; + } + break; + case 'color': + if (typeProcessor.hasOwnProperty(test)) { + tksetting = t.get(`aura${['1', '2'].includes(c.ident) ? c.ident : '1'}_radius`); + if (tksetting && tksetting.length) { + comp = [t.get(`aura${['1', '2'].includes(c.ident) ? c.ident : '1'}_color`), c.value]; + } + } + break; + case 'gmnotes': + if (typeProcessor.hasOwnProperty(test)) { + comp = [t.get(`gmnotes`), c.value]; + } + break; + case 'tip': + if (typeProcessor.hasOwnProperty(test)) { + comp = [t.get(`tooltip`), c.value]; + } + break; + case 'layer': + if (typeProcessor.hasOwnProperty(test)) { + comp = [t.get(`layer`), c.value]; + } + break; + case 'marker': + tksetting = getStatus(t, c.ident, msgId); + if (typeProcessor.hasOwnProperty(test)) { + comp = [tksetting.num, c.value]; + } else { // testing presence of marker + test = '='; + comp = [tksetting.is === 'yes', true]; + } + break; + case 'tag': + if (t.get('represents') && t.get('represents').length) { + let char = getObj('character', t.get('represents')); + if (char) { // testing presence of attribute + tksetting = JSON.parse(char.get('tags')); + test = '='; + comp = [tksetting.includes(c.ident), true]; + } + } + break; + case 'attribute': + if (t.get('represents') && t.get('represents').length) { + attrres = /^(?[^.|#?]+?)(?:(?:\.|\?|#|\|)(?current|cur|c|max|m))?\s*$/i.exec(c.ident); + if (attrres.groups && attrres.groups.attrval && attrres.groups.attrval.length && ['max', 'm'].includes(attrres.groups.attrval)) { + attrret = 'max'; + } + if (typeProcessor.hasOwnProperty(test)) { + attrval = (findObjs({ type: 'attribute', characterid: t.get('represents') }).filter(a => a.get('name') === attrres.groups.attr)[0] || { get: () => { return '' } }).get(attrret) || ''; + comp = [attrval, c.value]; + } else { // testing presence of attribute + test = '='; + comp = [findObjs({ type: 'attribute', characterid: t.get('represents') }).filter(a => a.get('name') === attrres.groups.attr).length > 0, true]; + } + } + break; + case 'type': + if (typeProcessor.hasOwnProperty(test)) { + if (c.value === 'graphic') { + tksetting = t.get('type'); + } else { + tksetting = t.get('type') === 'graphic' ? t.get('subtype') : t.get('type'); + } + comp = [tksetting, c.value]; + } + break; + case 'pc': + if (t.get('type') === 'graphic' && t.get('subtype') === 'token' && t.get('layer') === 'objects') { + test = '='; + comp = [isPlayerToken(t, true), true]; + } + break; + case 'npc': + if (t.get('type') === 'graphic' && t.get('subtype') === 'token') { + test = '='; + comp = [isNPC(t), true]; + } + break; + case 'pt': + if (t.get('type') === 'graphic' && t.get('subtype') === 'token' && t.get('layer') === 'objects') { + test = '='; + comp = [isPlayerToken(t, true), false]; + } + break; + case 'side': + if (typeProcessor.hasOwnProperty(test) && t.get('type') === 'graphic') { + tksetting = t.get('currentSide'); + comp = [tksetting, c.value]; + } + break; + default: + return false; + } + if (!comp.length) return false; + let result = typeProcessor[test](comp); + return c.musthave ? result : !result; + }; + + class Criteria { + constructor({ + type: type = '', + musthave: musthave = '', + ident: ident = '', + test: test = '', + value: value = '' + } = {}) { + this.type = type; + this.musthave = musthave; + this.ident = ident; + this.test = test; + this.value = value; + } + } + const injectrx = /(\()?{&\s*inject\s+([^}]+?)\s*}((?<=\({&\s*inject\s+([^}]+?)\s*})\)|\1)/gi; + const selectrx = /(\()?{&\s*select\s+([^}]+?)\s*}((?<=\({&\s*select\s+([^}]+?)\s*})\)|\1)/gi; + const criteriarx = /^(?\+|-)(?@|\*|#)?(?[^\s><=!~]+)(?:\s*$|\s*(?>=|<=|~|!~|=|!=|<|>|in(?=\s+\[[^\]]+\]))\s*(?.+)$)/; + const typeitemrx = /^(?bar|max|aura|color|layer|tip|gmnotes|type|pc|npc|pt|side)(?1|2|3|4)?(?<=(?:bar|max)\d|(?:aura|color)[1,2]|(?:layer|tip|gmnotes|type|pc|npc|pt|side))$/i; + const inject = (msg, status, msgId/*, notes*/) => { + const layerCriteria = (criteria) => { + return criteria.filter(c => c.type === 'layer').length ? true : false; + }; + const caseLibrary = [ + { rx: /^(\+|-)[^\s]+\s+in\s+\[$/i, terminator: ']' } + ]; + const getGroups = (cmd, index = 0, groups = []) => { + const getNextGroup = (cmd, terminator = ',') => { + let s = ''; + let bstop = false; + while (index <= cmd.length - 1 && !bstop) { + if (cmd.charAt(index) === terminator) { + if (terminator !== ',') { + s = `${s}${terminator}`; + index++; + } + bstop = true; + } else { + if (s.length || cmd.charAt(index) !== ' ') { + s = `${s}${cmd.charAt(index)}`; + } + index++; + for (const c of caseLibrary) { + c.rx.lastIndex = 0; + if (c.rx.test(s)) { + s = `${s}${getNextGroup(cmd, c.terminator)}`; + } + } + } + } + return s; + }; + while (index <= cmd.length - 1) { + groups.push(getNextGroup(cmd)); + index++; + } + return groups; + }; + const unpackGroups = (array) => { + return array + .map(l => getTokens(l, msg.playerid)) + .reduce((m, group) => { + m = [...m, ...group]; + return m; + }, []) + .filter(t => typeof t !== 'undefined'); + }; + const replaceOps = (rx, rxtype) => { + rx.lastIndex = 0; + msg.content = msg.content.replace(rx, (m, padding, group) => { + if (rxtype === 'inject') { + msg.selected = msg.selected || []; + } else if (rxtype === 'select') { + msg.selected = []; + } + let identifiers = getGroups(group) + .reduce((m, v) => { + if (criteriarx.test(v) && !findObjs({ id: v }).length) { + let critres = criteriarx.exec(v); + let newcriteria = new Criteria({ musthave: (critres.groups.musthave === '+'), test: (critres.groups.test || ''), value: checkTicks((critres.groups.value || '')) }); + if (critres.groups.attr && critres.groups.attr === '@') { + newcriteria.type = 'attribute'; + newcriteria.ident = (critres.groups.typeitem || ''); + } else if (critres.groups.attr && critres.groups.attr === '*') { + newcriteria.type = 'marker'; + newcriteria.ident = (critres.groups.typeitem || ''); + } else if (critres.groups.attr && critres.groups.attr === '#') { + newcriteria.type = 'tag'; + newcriteria.ident = (critres.groups.typeitem || ''); + } else if (typeitemrx.test(critres.groups.typeitem)) { + let ti_res = typeitemrx.exec(critres.groups.typeitem); + newcriteria.type = ti_res.groups.type; + newcriteria.ident = ti_res.groups.ident; + } else if (oldmarkerrx.test(v)) { + newcriteria.type = 'marker'; + newcriteria.ident = critres.groups.typeitem; + } else { + m.selections.push(v); + } + m.criteria.push(newcriteria); + } else { + m.selections.push(v); + } + return m; + }, { criteria: [], selections: [] }); + if (playerIsGM(msg.playerid) && !layerCriteria(identifiers.criteria)) { + identifiers.criteria.push(new Criteria({ type: 'layer', musthave: true, test: '=', value: 'objects' })); + } + identifiers.selections = uniqueArrayByProp(unpackGroups(identifiers.selections), 'id') + .filter(t => { + return identifiers.criteria.every(c => evaluateCriteria(c, t, msgId)); + }); + + msg.selected = identifiers.selections + .map(t => { return { '_id': t.id, '_type': t.get('type') }; }) + .reduce((m, t) => { + if (!m.map(mt => mt._id).includes(t._id)) { + m.push(t); + } + return m; + }, msg.selected); + + status.push('changed'); + return ''; + }); + }; + let retResult = false; + // handle selections + if (selectrx.test(msg.content)) { + retResult = true; + replaceOps(selectrx, 'select'); + } + // handle injections + if (injectrx.test(msg.content)) { + retResult = true; + replaceOps(injectrx, 'inject'); + } + if (msg.selected && !msg.selected.length) delete msg.selected; + return retResult; + }; + + const dispatchForSelected = (trigger, i) => { + if (preservedMsgObj[trigger].selected.length > i) { + sendChat(preservedMsgObj[trigger].chatSpeaker, `!${trigger}${i} ${preservedMsgObj[trigger].dsmsg.replace(/{&\s*i\s*((\+|-)\s*([\d]+)){0,1}}/gi, ((m, g1, op, val) => { return !g1 ? i : op === '-' ? parseInt(i) - parseInt(val) : parseInt(i) + parseInt(val); }))}`); + } + if (preservedMsgObj[trigger].selected.length <= i + 1) { + setTimeout(() => { delete preservedMsgObj[trigger] }, 10000); + } + }; + const fsrx = /(^!forselected(--|\+\+|\+-|-\+|\+|-|)(?:\((.)\)){0,1}(-silent)?\s+!?).+/i; + const forselected = (msg, apitrigger) => { + apitrigger = `${apiproject}${generateUUID()}`; + if (!(preservedMsgObj[maintrigger].selected && preservedMsgObj[maintrigger].selected.length)) { + let fsres = fsrx.exec(msg.content); + if (fsres && !fsres[4]) { // account for silent output + msgbox({ msg: `No selected tokens to use for that command. Please select some tokens then try again.`, title: `NO TOKENS`, whisperto: getWhisperTo(preservedMsgObj[maintrigger].who) }); + } + return; + } + preservedMsgObj[apitrigger] = { + selected: [...(preservedMsgObj[maintrigger].selected || [])], + who: preservedMsgObj[maintrigger].who, + playerid: preservedMsgObj[maintrigger].playerid, + dsmsg: '' + }; + preservedMsgObj[apitrigger].chatSpeaker = getTheSpeaker(preservedMsgObj[apitrigger]).chatSpeaker; + let fsres = fsrx.exec(msg.content); + switch (fsres[2] || '++') { + case '+-': + preservedMsgObj[apitrigger].replaceid = true; + preservedMsgObj[apitrigger].replacename = false; + break; + case '-': + case '-+': + preservedMsgObj[apitrigger].replaceid = false; + preservedMsgObj[apitrigger].replacename = true; + preservedMsgObj[apitrigger].nametoreplace = findObjs({ id: preservedMsgObj[apitrigger].selected[0]._id })[0].get('name'); + break; + case '--': + preservedMsgObj[apitrigger].replaceid = false; + preservedMsgObj[apitrigger].replacename = false; + break; + case '+': + case '++': + default: + preservedMsgObj[apitrigger].replaceid = true; + preservedMsgObj[apitrigger].replacename = true; + preservedMsgObj[apitrigger].nametoreplace = findObjs({ id: preservedMsgObj[apitrigger].selected[0]._id })[0].get('name'); + break; + } + msg.content = msg.content.replace(/\n/g, ' '); + preservedMsgObj[apitrigger].dsmsg = msg.content.slice(fsres[1].length); + if (fsres[3]) { + preservedMsgObj[apitrigger].dsmsg = preservedMsgObj[apitrigger].dsmsg.replace(new RegExp(escapeRegExp(fsres[3]), 'g'), ''); + } + dispatchForSelected(apitrigger, 0); + //preservedMsgObj[apitrigger].selected.forEach((t, i) => { + // sendChat(chatSpeaker, `!${apitrigger}${i} ${dsmsg.replace(/{&\s*i\s*((\+|-)\s*([\d]+)){0,1}}/gi, ((m, g1, op, val) => { return !g1 ? i : op === '-' ? parseInt(i) - parseInt(val) : parseInt(i) + parseInt(val); }))}`); + //}); + //setTimeout(() => { delete preservedMsgObj[apitrigger] }, 10000); + }; + const trackprops = (msg) => { + [ + preservedMsgObj[maintrigger].who, + preservedMsgObj[maintrigger].selected, + preservedMsgObj[maintrigger].playerid, + preservedMsgObj[maintrigger].inlinerolls + ] = [msg.who, msg.selected, msg.playerid, msg.inlinerolls]; + }; + const handleInput = (msg, msgstate = {}) => { + let funcret = { runloop: false, status: 'unchanged', notes: '' }; + const trigrx = new RegExp(`^!(${Object.keys(preservedMsgObj).join('|')})`); + let apitrigger; // the apitrigger used by the message + if (!Object.keys(msgstate).length && scriptisplugin) return funcret; + let status = []; + let notes = []; + let msgId = generateUUID(); + msg.content = msg.content.replace(/\n/g, '({&br-sm})'); + let injection = inject(msg, status, msgId, notes); + if ('API' !== msg.playerid) { // user generated message + trackprops(msg); + } else { // API generated message + if (injection) preservedMsgObj[maintrigger].selected = msg.selected; + // peel off ZeroFrame trigger, if it's there + if (msg.apitrigger) msg.content = msg.content.replace(msg.apitrigger, ''); + if (trigrx.test(msg.content)) { // message has apitrigger (iterative call of forselected) so cycle-in next selected + apitrigger = trigrx.exec(msg.content)[1]; + msg.content = msg.content.replace(apitrigger, ''); + status.push('changed'); + let nextindex = /^!(\d+)\s*/.exec(msg.content)[1]; + msg.content = `!${msg.content.slice(nextindex.length + 2)}`; + nextindex = Number(nextindex); + msg.selected = []; + msg.selected.push(preservedMsgObj[apitrigger].selected[nextindex]); + msg.who = preservedMsgObj[apitrigger].who; + msg.playerid = preservedMsgObj[apitrigger].playerid; + // handle replacements of @{selected|token_id} and @{selected|token_name} + if (preservedMsgObj[apitrigger].replaceid) { + msg.content = msg.content.replace(apitrigger, '').replace(preservedMsgObj[apitrigger].selected[0]._id, msg.selected[0]._id); + } + if (preservedMsgObj[apitrigger].replacename && preservedMsgObj[apitrigger].nametoreplace && msg.selected[0]._type === 'graphic') { + msg.content = msg.content.replace(apitrigger, '').replace(preservedMsgObj[apitrigger].nametoreplace, findObjs({ id: msg.selected[0]._id })[0].get('name')); + } + // handle replacements of at{selected|prop} + if (typeof Fetch !== 'undefined' && typeof ZeroFrame !== 'undefined') { + const fetchselrx = /at\((?selected)[|.](?[^\s[|.)]+?)(?:[|.](?[^\s.[|]+?)){0,1}(?:\[(?[^\]]*?)]){0,1}\s*\)/gi; + const fetchrptgselrx = /at\((?selected)[|.](?
    [^\s.|]+?)[|.]\[\s*(?.+?)\s*]\s*[|.](?[^[\s).]+?)(?:[|.](?[^\s.[)]+?)){0,1}(?:\[(?[^\]]*?)]){0,1}\s*\)/gi; + msg.content = msg.content.replace(fetchselrx, m => { + status.push('changed') + return `@${m.slice(2)}`; + }); + msg.content = msg.content.replace(fetchrptgselrx, m => { + status.push('changed') + return `*${m.slice(2)}`; + }); + } else { + let selrx = /at{selected(?:\||\.)([^|}]+)(\|max)?}/ig; + let retval; + msg.content = msg.content.replace(selrx, (g0, g1, g2) => { + if (['token_id', 'token_name', 'bar1', 'bar2', 'bar3', 'bar4'].includes(g1.toLowerCase())) { + let tok = findObjs({ id: msg.selected[0]._id })[0]; + if (g1.toLowerCase() === 'token_id') retval = tok.id; + else if (g1.toLowerCase() === 'token_name') retval = tok.get('name'); + else retval = tok.get(`${g1}_${g2 ? 'max' : 'value'}`) || ''; + } else { + let character = findObjs({ type: 'character', id: (getObj("graphic", msg.selected[0]._id) || { get: () => { return "" } }).get("represents") })[0]; + if (!character) { + notes.push('No character found represented by token ${msg.selected[0]._id}'); + status.push('unresolved'); + retval = ''; + } else if ('character_id' === g1.toLowerCase()) { + retval = character.id; + } else if ('character_name' === g1.toLowerCase()) { + retval = character.get('name'); + } + status.push('changed'); + retval(findObjs({ type: 'attribute', characterid: character.id })[0] || { get: () => { return '' } }).get(g2 ? 'max' : 'current') || ''; + } + }); + } + dispatchForSelected(apitrigger, nextindex + 1); + } else { // api generated call to another script, copy in the appropriate data + if (manageState.get('autoinsert').includes('selected')) { + if (preservedMsgObj[maintrigger].selected && preservedMsgObj[maintrigger].selected.length) { + msg.selected = preservedMsgObj[maintrigger].selected; + } + if (!msg.selected || (msg.selected && !msg.selected.length)) { + delete msg.selected; + } + } + if (manageState.get('autoinsert').includes('who') && !manageState.get('knownsenders').includes(msg.who)) { + msg.who = preservedMsgObj[maintrigger].who; + } + if (manageState.get('autoinsert').includes('playerid')) { + msg.playerid = preservedMsgObj[maintrigger].playerid; + } + } + // replace ZeroFrame trigger, if it's there + if (msg.apitrigger) msg.content = `!${msg.apitrigger}${msg.content.slice(1)}`; + } + msg.content = msg.content.replace(/\({&br-sm}\)/g, '
    \n'); + return condensereturn(funcret, status, notes); + }; + const handleForSelected = (msg) => { + if (msg.type !== 'api' || !fsrx.test(msg.content)) return; + forselected(msg); + }; + const getProp = (prop) => { + return preservedMsgObj[maintrigger][prop] || undefined; + }; + const getSelected = () => getProp('selected'); + const getWho = () => getProp('who'); + const getPlayerID = () => getProp('playerid'); + + const checkDependencies = (deps) => { + /* pass array of objects like + { name: 'ModName', version: '#.#.#' || '', mod: ModName || undefined, checks: [ [ExposedItem, type], [ExposedItem, type] ] } + */ + const dependencyEngine = (deps) => { + const versionCheck = (mv, rv) => { + let modv = [...mv.split('.'), ...Array(4).fill(0)].slice(0, 4); + let reqv = [...rv.split('.'), ...Array(4).fill(0)].slice(0, 4); + return reqv.reduce((m, v, i) => { + if (m.pass || m.fail) return m; + if (i < 3) { + if (parseInt(modv[i]) > parseInt(reqv[i])) m.pass = true; + else if (parseInt(modv[i]) < parseInt(reqv[i])) m.fail = true; + } else { + // all betas are considered below the release they are attached to + if (reqv[i] === 0 && modv[i] === 0) m.pass = true; + else if (modv[i] === 0) m.pass = true; + else if (reqv[i] === 0) m.fail = true; + else if (parseInt(modv[i].slice(1)) >= parseInt(reqv[i].slice(1))) m.pass = true; + } + return m; + }, { pass: false, fail: false }).pass; + }; + + let result = { passed: true, failures: {}, optfailures: {} }; + deps.forEach(d => { + let failObj = d.optional ? result.optfailures : result.failures; + if (!d.mod) { + if (!d.optional) result.passed = false; + failObj[d.name] = 'Not found'; + return; + } + if (d.version && d.version.length) { + if (!(API_Meta[d.name].version && API_Meta[d.name].version.length && versionCheck(API_Meta[d.name].version, d.version))) { + if (!d.optional) result.passed = false; + failObj[d.name] = `Incorrect version. Required v${d.version}. ${API_Meta[d.name].version && API_Meta[d.name].version.length ? `Found v${API_Meta[d.name].version}` : 'Unable to tell version of current.'}`; + return; + } + } + d.checks.reduce((m, c) => { + if (!m.passed) return m; + let [pname, ptype] = c; + if (!d.mod.hasOwnProperty(pname) || typeof d.mod[pname] !== ptype) { + if (!d.optional) m.passed = false; + failObj[d.name] = `Incorrect version.`; + } + return m; + }, result); + }); + return result; + }; + let depCheck = dependencyEngine(deps); + let failures = '', contents = '', msg = ''; + if (Object.keys(depCheck.optfailures).length) { // optional components were missing + failures = Object.keys(depCheck.optfailures).map(k => `• ${k} : ${depCheck.optfailures[k]}`).join('
    '); + contents = `${apiproject} utilizies one or more other scripts for optional features, and works best with those scripts installed. You can typically find these optional scripts in the 1-click Mod Library:
    ${failures}`; + msg = `
    MISSING MOD DETECTED
    ${contents}
    `; + sendChat(apiproject, `/w gm ${msg}`); + } + if (!depCheck.passed) { + failures = Object.keys(depCheck.failures).map(k => `• ${k} : ${depCheck.failures[k]}`).join('
    '); + contents = `${apiproject} requires other scripts to work. Please use the 1-click Mod Library to correct the listed problems:
    ${failures}`; + msg = `
    MISSING MOD DETECTED
    ${contents}
    `; + sendChat(apiproject, `/w gm ${msg}`); + return false; + } + return true; + }; + + + let scriptisplugin = false; + const selectmanager = (m, s) => handleInput(m, s); + on('chat:message', handleInput); + setTimeout(() => { on('chat:message', handleForSelected) }, 0); + on('ready', () => { + versionInfo(); + logsig(); + let reqs = [ + { + name: 'libTokenMarkers', + version: `0.1.2`, + mod: typeof libTokenMarkers !== 'undefined' ? libTokenMarkers : undefined, + checks: [['getStatus', 'function'], ['getStatuses', 'function'], ['getOrderedList', 'function']] + }, + { + name: 'Messenger', + version: `1.0.0`, + mod: typeof Messenger !== 'undefined' ? Messenger : undefined, + checks: [['Button', 'function'], ['MsgBox', 'function'], ['HE', 'function'], ['Html', 'function'], ['Css', 'function']] + } + ]; + if (!checkDependencies(reqs)) return; + html = Messenger.Html(); + css = Messenger.Css(); + HE = Messenger.HE; + + oldmarkerrx = new RegExp(`^(\\+|-)(${libTokenMarkers.getOrderedList().map(o => o.name).join('|')})`); + + issueVersionUpdateMessages(); + + scriptisplugin = (typeof ZeroFrame !== `undefined`); + if (typeof ZeroFrame !== 'undefined') { + ZeroFrame.RegisterMetaOp(selectmanager, { priority: 20, handles: ['sm'] }); + } + on('chat:message', handleConfig); + }); + + return { // public interface + GetSelected: getSelected, + GetWho: getWho, + GetPlayerID: getPlayerID + }; + +})(); +{ try { throw new Error(''); } catch (e) { API_Meta.SelectManager.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.SelectManager.offset); } } +/* */ diff --git a/SelectManager/SelectManager.js b/SelectManager/SelectManager.js index 44e9a58b53..4e7f5672ec 100644 --- a/SelectManager/SelectManager.js +++ b/SelectManager/SelectManager.js @@ -3,8 +3,8 @@ Name : SelectManager GitHub : https://github.com/TimRohr22/Cauldron/tree/master/SelectManager Roll20 Contact : timmaugh && TheAaron -Version : 1.1.9 -Last Update : 14 JULY 2025 +Version : 1.1.10 +Last Update : 6 OCT 2025 ========================================================= */ var API_Meta = API_Meta || {}; @@ -16,12 +16,12 @@ const SelectManager = (() => { //eslint-disable-line no-unused-vars // VERSION // ================================================== const apiproject = 'SelectManager'; - const version = '1.1.9'; + const version = '1.1.10'; const schemaVersion = 0.4; const apilogo = 'https://i.imgur.com/ewyOzMU.png'; const apilogoalt = 'https://i.imgur.com/3U8c9rE.png' API_Meta[apiproject].version = version; - const vd = new Date(1752538456318); + const vd = new Date(1759763868181); const versionInfo = () => { log(`\u0166\u0166 ${apiproject} v${API_Meta[apiproject].version}, ${vd.getFullYear()}/${vd.getMonth() + 1}/${vd.getDate()} \u0166\u0166 -- offset ${API_Meta[apiproject].offset}`); if (!state.hasOwnProperty(apiproject) || state[apiproject].version !== schemaVersion) { @@ -589,12 +589,12 @@ const SelectManager = (() => { //eslint-disable-line no-unused-vars switch (c.type) { case 'bar': if (typeProcessor.hasOwnProperty(test)) { - comp = [t.get(`bar${['1', '2', '3'].includes(c.ident) ? c.ident : '1'}_value`), c.value]; + comp = [t.get(`bar${['1', '2', '3', '4'].includes(c.ident) ? c.ident : '1'}_value`), c.value]; } break; case 'max': if (typeProcessor.hasOwnProperty(test)) { - comp = [t.get(`bar${['1', '2', '3'].includes(c.ident) ? c.ident : '1'}_max`), c.value]; + comp = [t.get(`bar${['1', '2', '3', '4'].includes(c.ident) ? c.ident : '1'}_max`), c.value]; } break; case 'aura': @@ -726,7 +726,7 @@ const SelectManager = (() => { //eslint-disable-line no-unused-vars const injectrx = /(\()?{&\s*inject\s+([^}]+?)\s*}((?<=\({&\s*inject\s+([^}]+?)\s*})\)|\1)/gi; const selectrx = /(\()?{&\s*select\s+([^}]+?)\s*}((?<=\({&\s*select\s+([^}]+?)\s*})\)|\1)/gi; const criteriarx = /^(?\+|-)(?@|\*|#)?(?[^\s><=!~]+)(?:\s*$|\s*(?>=|<=|~|!~|=|!=|<|>|in(?=\s+\[[^\]]+\]))\s*(?.+)$)/; - const typeitemrx = /^(?bar|max|aura|color|layer|tip|gmnotes|type|pc|npc|pt|side)(?1|2|3)?(?bar|max|aura|color|layer|tip|gmnotes|type|pc|npc|pt|side)(?1|2|3|4)?(?<=(?:bar|max)\d|(?:aura|color)[1,2]|(?:layer|tip|gmnotes|type|pc|npc|pt|side))$/i; const inject = (msg, status, msgId/*, notes*/) => { const layerCriteria = (criteria) => { return criteria.filter(c => c.type === 'layer').length ? true : false; @@ -967,7 +967,7 @@ const SelectManager = (() => { //eslint-disable-line no-unused-vars let selrx = /at{selected(?:\||\.)([^|}]+)(\|max)?}/ig; let retval; msg.content = msg.content.replace(selrx, (g0, g1, g2) => { - if (['token_id', 'token_name', 'bar1', 'bar2', 'bar3'].includes(g1.toLowerCase())) { + if (['token_id', 'token_name', 'bar1', 'bar2', 'bar3', 'bar4'].includes(g1.toLowerCase())) { let tok = findObjs({ id: msg.selected[0]._id })[0]; if (g1.toLowerCase() === 'token_id') retval = tok.id; else if (g1.toLowerCase() === 'token_name') retval = tok.get('name'); diff --git a/SelectManager/script.json b/SelectManager/script.json index 60dfb22817..eb007c118a 100644 --- a/SelectManager/script.json +++ b/SelectManager/script.json @@ -1,7 +1,7 @@ { "name": "SelectManager", "script": "SelectManager.js", - "version": "1.1.9", + "version": "1.1.10", "description": "SelectManager stores the selected, who, and playerid properties from the last user-generated message (as opposed to API generated), and makes them available for another script to retrieve. This solves the problem of an API-generated message not having the original array of selected tokens, for instance. \r\rIt also provides a !forselected handle to iterate over the selected tokens, firing off an individual call to another script for each token in turn, making each the selected token.\r\rFinally, it offers a way to virtually select tokens for the message, or to inject tokens into the selected token array.\r\rFor more information, see the original thread in the API forum:\r\r[SelectManager Forum Thread](https://app.roll20.net/forum/post/9817678/script-selectmanager-update-brings-forselected-iteration-and-gives-user-new-control-to-give-selected-tokens-back-to-api-generated-messages)\r\rOr read about the full set of meta-scripts available: \r\r[Meta Toolbox Forum Thread](https://app.roll20.net/forum/post/10005695/script-set-the-meta-toolbox)", "authors": "timmaugh, The Aaron", "roll20userid": "5962076, 104025", @@ -33,6 +33,7 @@ "1.1.5", "1.1.6", "1.1.7", - "1.1.8" + "1.1.8", + "1.1.10" ] }