";
+
+
+ let testChar = findObjs({
+ type: 'character'
+ });
+
+
+ //intercept shorthand phrases that replace entire calls
+ switch (msg.content) {
+ case '!report|mapkeys':
+ mapKeyChar = findObjs({
+ type: 'character',
+ name: 'Map Key'
+ })[0];
+
+ if (mapKeyChar) {
+ mapKey = mapKeyChar.get('_id');
+ msg.content = `!report||+|t|represents|${mapKey} --- ---- layer|gmlayer compact|true charactersheetlink|false notesbutton|true showprintbutton|false title|Map Keys| tokennotesbutton|true ignoreselected|true showheader|false `;
+ //if (reportName){msg.content = `${msg.content} handout|${reportName}|`};
+ } else {
+ msg.content = `!report||+|t|thereisnocharacterbythisname`
+ }
+ break;
+ case '!report|mapkeys_sorted':
+ mapKeyChar = findObjs({
+ type: 'character',
+ name: 'Map Key'
+ })[0];
+ if (mapKeyChar) {
+ mapKey = mapKeyChar.get('_id');
+ msg.content = `!report||+|t|represents|${mapKey} --- ---- layer|gmlayer compact|true sort|tokenName charactersheetlink|false notesbutton|true showprintbutton|false title|Map Keys| tokennotesbutton|true ignoreselected|true showheader|false `;
+ //if (reportName){msg.content = `${msg.content} handout|${reportName}|`};
+ } else {
+ msg.content = `!report||+|t|thereisnocharacterbythisname`
+ }
+ break;
+
+ case '!report|pcs-detail':
+
+ switch (sheet) {
+ case 'Other':
+ msg.content = `!report||+|t|thereisnocharacterbythisname`
+ sendChat('Reporter', toWhom + openReport + sheet + 'This command only works on a supported sheet. Click here to choose a supported sheet or forego using this preset command
[Configure](!report --config|sheet)' + closeReport, null, { noarchive: true });
+ break;
+ case 'D&D 5E Shaped':
+ msg.content = `!report||+|c|is_npc|0 --c|strength,|Str c|dexterity,|Dex c|constitution|Con c|intelligence,|Int c|wisdom,|Wis c|charisma#|Cha c|hp,|hp c|ac,|AC c|gp|GP ---vision ---- showfooter|false showheader|false printbutton|false hideempty|true source|false title|PCs Detail|`;
+ break;
+ case 'Pathfinder First Edition by Roll20':
+ msg.content = `!report||+|c|npc|0 --detail ---vision ---- showfooter|false showheader|false printbutton|false hideempty|true source|false title|PCs Detail|`;
+ break;
+ case 'Pathfinder Second Edition by Roll20':
+ msg.content = `!report||-|c|sheet_type|npc --detail ---vision ---- showfooter|false showheader|false printbutton|false hideempty|true source|false title|PCs Detail|`;
+ break;
+ case 'D&D 5th Edition by Roll20':
+ msg.content = `!report||+|c|npc|0 --c|strength,|Str c|dexterity,|Dex c|constitution|Con c|intelligence,|Int c|wisdom,|Wis c|charisma#|Cha c|hp,|hp c|ac,|AC c|gp|GP ---vision ---- showfooter|false showheader|false printbutton|false hideempty|true source|false title|PCs Detail|`;
+ break;
+ default:
+ break;
+ }
+ break;
+
+
+
+ case '!report|pcs':
+ switch (sheet) {
+ case 'Other':
+ msg.content = `!report||+|t|thereisnocharacterbythisname`
+ sendChat('Reporter', toWhom + openReport + sheet + 'This command only works on a supported sheet. Click here to choose a supported sheet or forego using this preset command
[Configure](!report --config|sheet)' + closeReport, null, { noarchive: true });
+ break;
+ case 'D&D 5E Shaped':
+ msg.content = `!report||+|c|is_npc|0 ---- title|PC Directory| sort|bar1 compact|true showheader|false showfooter|false layer|objects charactersheetbutton|true ignoreselected|true printbutton|false`;
+ break;
+ case 'Pathfinder First Edition by Roll20':
+ msg.content = `!report||+|c|npc|0 ---- title|PC Directory| sort|bar1 compact|true showheader|false showfooter|false layer|objects charactersheetbutton|true ignoreselected|true printbutton|false`;
+ break;
+ case 'Pathfinder Second Edition by Roll20':
+ msg.content = `!report||-|c|sheet_type|npc ---- title|PC Directory| sort|bar1 compact|true showheader|false showfooter|false layer|objects charactersheetbutton|true ignoreselected|true printbutton|false`;
+ break;
+ case 'D&D 5th Edition by Roll20':
+ msg.content = `!report||+|c|npc|0 ---- title|PC Directory| sort|bar1 compact|true showheader|false showfooter|false layer|objects charactersheetbutton|true ignoreselected|true printbutton|false`;
+ break;
+ default:
+ break;
+ }
+ break;
+
+
+ case '!report|npcs-detail':
+ switch (sheet) {
+ case 'Other':
+ msg.content = `!report||+|t|thereisnocharacterbythisname`
+ sendChat('Reporter', toWhom + openReport + sheet + 'This command only works on a supported sheet. Click here to choose a supported sheet or forego using this preset command
[Configure](!report --config|sheet)' + closeReport, null, { noarchive: true });
+ break;
+ case 'Pathfinder First Edition by Roll20':
+ msg.content = `!report||+|c|npc|1 --detail ---vision ---- showfooter|false showheader|false printbutton|false hideempty|true source|false title|NPCs Detail|`;
+ break;
+ case 'Pathfinder Second Edition by Roll20':
+ msg.content = `!report||+|c|sheet_type|npc --detail ---vision ---- showfooter|false showheader|false printbutton|false hideempty|true source|false title|NPCs Detail|`;
+ break;
+ case 'D&D 5E Shaped':
+ msg.content = `!report||-|c|is_npc|0 --detail ---- showfooter|false showheader|false source|false printbutton|false hideempty|true charactersheetbutton|true title|NPCs Detail| `;
+ break;
+ case 'D&D 5th Edition by Roll20':
+ msg.content = `!report||-|c|npc|0 --detail ---- showfooter|false showheader|false source|false printbutton|false hideempty|true charactersheetbutton|true title|NPCs Detail| `;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case '!report|npcs-actions':
+ switch (sheet) {
+ case 'Other':
+ msg.content = `!report||+|t|thereisnocharacterbythisname`
+ sendChat('Reporter', toWhom + openReport + sheet + 'This command only works on a supported sheet. Click here to choose a supported sheet or forego using this preset command
[Configure](!report --config|sheet)' + closeReport, null, { noarchive: true });
+ break;
+ case 'Pathfinder First Edition by Roll20':
+ msg.content = `!report||+|c|npc|1 -- ---actions ---- showfooter|false showheader|false source|false printbutton|false compact|true charactersheetbutton|true title|NPC Actions| `;
+ break;
+ case 'Pathfinder Second Edition by Roll20':
+ msg.content = `!report||+|c|sheet_type|npc -- ---actions ---- showfooter|false showheader|false source|false printbutton|false compact|true charactersheetbutton|true title|NPC Actions| `;
+ break;
+ case 'D&D 5th Edition by Roll20':
+ msg.content = `!report||-|c|npc|0 -- ---actions ---- showfooter|false showheader|false source|false printbutton|false compact|true charactersheetbutton|true title|NPC Actions| `;
+ break;
+ case 'D&D 5E Shaped':
+ msg.content = `!report||-|c|is_npc|0 -- ---actions ---- showfooter|false showheader|false source|false printbutton|false compact|true charactersheetbutton|true title|NPC Actions| `;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case '!report|tracker':
+ msg.content = `!report --t|statusmarkers|- ---- showfooter|false showheader|false source|false title|Tracker Compact| layer|tracker compact|true hideempty|true charactersheetbutton|true `;
+ break;
+ case '!report|tracker-actions':
+ msg.content = `!report -- ---actions ---- showfooter|false showheader|false source|false title|Tracker Actions| layer|tracker compact|true charactersheetbutton|true `;
+ break;
+ case '!report|light':
+ msg.content = `!report --light ---lightplus ----showsource|false`;
+ break;
+ case '!report|vision':
+ msg.content = `!report --vision ---vision ----showsource|false`;
+ break;
+ case '!report|help':
+ msg.content = `!report --help`;
+ break;
+ case '!report|menu':
+ msg.content = `!report --menu`;
+ break;
+ case '!report|report':
+ msg.content = `!report --report`;
+ break;
+
+ default:
+ }
+ // sendChat('Reporter', toWhom + openReport + msg.content + closeReport, null, { noarchive: true });
+
+
+
+
+ if (msg.content === "!report --mapkeys") {
+ mapKeyChar = findObjs({
+ type: 'character',
+ name: 'Map Key'
+ })[0];
+ mapKey = mapKeyChar.get('_id');
+ msg.content = `!report||+|t|represents|${mapKey} --- ---- layer|gmlayer compact|true sort|tokennameI charactersheetlink|false notesbutton|true showprintbutton|false title|Map Keys| tokennotesbutton|true ignoreselected|true showheader|false `;
+ if (reportName) { msg.content = `${msg.content} handout|${reportName}` };
+ }
+
+
+ // ################## Keywords
+ let keywords = msg.content.split(/\s+----/)[1] || "";
+ let reportName = "";
+ if (keywords) {
+ if (keywords.includes("layer|")) {
+ currentLayer = keywords.split(/layer\|/)[1].split(/\s+/)[0] || "";
+ }
+ if (!("map objects walls gmlayer all tracker").includes(currentLayer) && undefined !== currentLayer) {
+ currentLayer = ""
+ }
+
+ compact = ((keywords.includes("compact|true")) ? true : false);
+ minimal = ((keywords.includes("minimal|")) ? true : false);
+ showFooter = ((keywords.includes("showfooter|false")) ? false : true);
+ showHeader = ((keywords.includes("showheader|false")) ? false : true);
+ showPageInfo = ((keywords.includes("showpageinfo|true")) ? true : false);
+ customTitle = ((keywords.includes("title|")) ? keywords.match(/title\|.*?\|/).toString().split("|")[1] : '');
+ customTitle = ((keywords.includes("overtitle|")) ? keywords.match(/title\|.*?\|/).toString().split("|")[1] : customTitle);
+ source = ((keywords.includes("source|false")) ? false : true);
+ isPrintbutton = ((keywords.includes("printbutton|true")) ? true : false);
+ isTokenNotesButton = ((keywords.includes("tokennotesbutton|true")) ? true : false);
+ isTokenNotesRow = ((keywords.includes("tokennotesrow|true")) ? true : false);
+ isTokenImageButton = ((keywords.includes("tokenbutton|true")) ? true : false);
+ isCharNotesButton = ((keywords.includes("charnotesbutton|true")) ? true : false);
+ isBioButton = ((keywords.includes("biobutton|true")) ? true : false);
+ isAvatarButton = ((keywords.includes("avatarbutton|true")) ? true : false);
+ isTooltipButton = ((keywords.includes("tooltipbutton|true")) ? true : false);
+ isLayerButton = ((keywords.includes("layerbutton|false")) ? false : true);
+ isImageButton = ((keywords.includes("imagebutton|true")) ? true : false);
+ subTitle = ((keywords.includes("subtitle|false")) ? false : true);
+ ignoreSelected = ((keywords.includes("ignoreselected|true")) || (keywords.includes("layer|tracker")) ? true : false);
+ toWhom = ((keywords.includes("visibility|whisper")) ? '/w ' + msg.who : '/w gm ');
+ toWhom = ((keywords.includes("visibility|all")) ? '' : toWhom);
+ noteRecipient = ((keywords.includes("visibility|all")) ? '!pcnote' : '!gmnote');
+ noteRecipient = ((keywords.includes("visibility|whisper")) ? '!selfnote' : noteRecipient);
+ npcSubstitutions = ((keywords.includes("npcsubstitutions|false")) ? false : true);
+ hideEmpty = ((keywords.includes("hideempty|true")) ? true : false);
+ characterSheetLink = ((keywords.includes("charactersheetlink|false")) ? false : true);
+ characterSheetButton = ((keywords.includes("charactersheetbutton|true")) ? true : false);
+ displayNotes = ((keywords.includes("displaynotes|true")) ? true : false);
+ scrolling = ((keywords.includes("scrolling|")) ? keywords.match(/(?<=scrolling\|)\d*/g) : false);
+ sidebar = ((keywords.includes("sidebar|")) ? keywords.match(/(?<=sidebar\|)left|right/) : false);
+ sortTerm = ((keywords.includes("sort|")) ? keywords.split("sort|")[1].split(" ")[0] : 'identity');
+ reportName = ((keywords.match(/handout\|.*?\|/)) ? keywords.match(/handout\|.*?\|/).toString().split("|")[1] : "");
+ if (keywords.includes("minimal|")) {
+ showHeader = false;
+ showFooter = false;
+ compact=true;
+ source = false;
+ characterSheetButton = true;
+ characterSheetLink = true;
+ hideEmpty = true;
+ if (!customTitle && !keywords.includes("minimal|true")) {
+
+customTitle = keywords.match(/minimal\|.*?$/).toString().split("|")[1];
+ } else {
+ customTitle = ((!customTitle) ? "Report" : customTitle);}
+}
+
+
+
+ if (keywords.includes("overtitle|")) {
+ showHeader = false;
+ showFooter = false
+ }
+ }
+
+ // ##### Assign selected if no tokens are selected
+ if (msg.selected && ignoreSelected === false) {
+ allLayers = false
+
+ selection = msg.selected
+ .map(o => getObj('graphic', o._id))
+ .filter(o => undefined !== o)
+ } else {
+ if (currentLayer === 'all' || currentLayer === 'tracker') {
+ allLayers = true
+
+ selection = findObjs({
+ type: 'graphic',
+ pageid: getPageForPlayer(msg.playerid),
+ //layer: currentLayer || 'objects'
+ });
+ } else {
+ allLayers = false
+
+ selection = findObjs({
+ type: 'graphic',
+ pageid: getPageForPlayer(msg.playerid),
+ layer: currentLayer || 'objects'
+ });
+
+ }
+ }
+
+ //gathers from tracker
+ var turnorder;
+ if (Campaign().get("turnorder") == "") turnorder = []; //NOTE: We check to make sure that the turnorder isn't just an empty string first. If it is treat it like an empty array.
+ else turnorder = JSON.parse(Campaign().get("turnorder"));
+
+ let tselection = turnorder
+ .map(o => getObj('graphic', o.id))
+ .filter(o => undefined !== o)
+
+
+
+ const repeatChar = `
`;
+
+ const args = msg.content.split(/\s+--/);
+
+ buttonLine = '';
+ let buttonCode = msg.content.split(/\s+---/)[1] //.split(/\s+----/)[0]; //gets everything between --- and ----
+ buttonCode = ((buttonCode && buttonCode.includes(" ----")) ? buttonCode.split(/\s+----/)[0] : buttonCode); //gets everything between --- and ----
+ if (undefined !== buttonCode) {
+ if (buttonCode.substring(0, 2) === "- ") {
+ buttonCode = ""
+ }
+ }
+
+ // Default buttonLines
+ switch (buttonCode) {
+ case 'vision':
+ buttonLine = '**Vision** [Off](!token-mod --set bright_vision|false has_night_vision|false)|[On](!token-mod --set bright_vision|true) **Night** [Off](!token-mod --set has_night_vision|false bright_vision|false)|[On](!token-mod --set bright_vision|true night_vision|true)|[Distance](!token-mod --set has_night_vision|true night_vision_distance|Q{Set night vision distance|60})'; // | **[SET DEFAULT TOKEN](!token-mod --set defaulttoken)**';
+ break;
+ case 'udl':
+ buttonLine = '**Vision** [Off](!token-mod --set bright_vision|false has_night_vision|false)|[On](!token-mod --set bright_vision|true) **Night** [Off](!token-mod --set has_night_vision|false bright_vision|false)|[On](!token-mod --set bright_vision|true night_vision|true)|[Distance](!token-mod --set has_night_vision|true night_vision_distance|Q{Set night vision distance|60})';
+ break;
+ case 'lightplus':
+ //Low Light requires Bright Light to be on, even if the distance is 0
+ buttonLine = '[Off](!token-mod --set emits_bright_light|off emits_low_light|off has_directional_bright_light|false has_directional_dim_light|false directional_bright_light_total|360 directional_dim_light_total|360) | [On](!token-mod --set emits_bright_light|on emits_low_light|on light_angle|360) | [Spot](!token-mod --set emits_bright_light|on bright_light_distance|5 low_light_distance|0 light_angle|360) | [Candle](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|2 low_light_distance|5 light_angle|360) | [Lamp](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|15 low_light_distance|15 light_angle|360) | [Torch](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|20 low_light_distance|20 light_angle|360) | [Hooded Lantern](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|30 low_light_distance|30 light_angle|360) | [Bullseye Lantern](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|60 has_directional_bright_light|true has_directional_dim_light|true directional_bright_light_total|60 directional_dim_light_total|60) | [*Light*](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|20 low_light_distance|20 light_angle|360) | [*Daylight*](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|60 low_light_distance|60 light_angle|360) | [*Dancing*](!token-mod --set emits_bright_light|off emits_low_light|on bright_light_distance|0 low_light_distance|10 light_angle|360) | [*Faerie Fire*](!token-mod --set emits_bright_light|off emits_low_light|on bright_light_distance|0 low_light_distance|10 statusmarkers|purple light_angle|360) | [*Gem of Brightness*](!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|30 low_light_distance|30 light_angle|360)';
+ break;
+ case 'light':
+ //Low Light requires Bright Light to be on, even if the distance is 0
+ buttonLine = '**Bright** [On](!token-mod --set emits_bright_light|on)|[Off](!token-mod --set emits_bright_light|off)|[Distance](!token-mod --set bright_light_distance|Q{Distance?|0}) - **Low** [On](!token-mod --set emits_low_light|on)|[Off](!token-mod --set emits_low_light|off)|[Distance](!token-mod --set low_distance|Q{Distance?|0})';
+ break;
+ default:
+ buttonLine = buttonCode;
+ }
+
+
+
+
+ //WIP Set Filters
+ let filters = (args[0].split(/\|\|/));
+
+ let bob = filters.shift();
+ let theFilter = (args[0].split(/[+-]/))[1];
+ let theFilter2 = (args[0].split(/[+-]/))[2] || "bob";
+ let filterOption = '';
+ let filterTerm = '';
+ let filterValue = '';
+ if (args[0].includes('!report||-')) filterOption = 'exclude';
+ if (args[0].includes('!report||+')) filterOption = 'restrict';
+
+
+
+ //Creating variables for an array of ids and an array of names.
+ let idList = [];
+ let nameList = [];
+ let lines = '';
+ let charType = '';
+ let pageData = [];
+ let layerChar = "";
+
+
+ let value = "";
+
+ selection = ((currentLayer === "tracker") ? tselection : selection)
+ let TCData = selection
+ .filter(t => t.get('represents').length)
+ .map(t => ({
+ token: t,
+ character: getObj('character', t.get('represents'))
+ }))
+ .filter(o => undefined !== o.character);
+
+
+
+ let filterTCData = TCData;
+ let newTCData = [];
+
+ filters.forEach(f => {
+
+
+
+ // Filter
+ if (f) {
+ filterOption = f.split(/\|/)[0];
+ if (filterOption === "-") filterOption = 'omit';
+ if (filterOption === "+") filterOption = 'restrict';
+ if (filterOption === "~") filterOption = 'include';
+ if (filterOption === "^") filterOption = 'exclude';
+
+ target = f.split(/\|/)[1];
+ attribute = f.split(/\|/)[2];
+ filterValue = f.split(/\|/)[3];
+
+ if (!filterValue) {
+ filterValue = " "
+ }
+ if (filterValue === "!") {
+ filterValue = ""
+ }
+
+ if (filterValue !== 'all') {
+ if (target === 't') {
+ if (filterOption === "omit") filterTCData = TCData.filter(o => cleanup(o.token.get(attribute)) !== cleanup(filterValue)); //WORKS
+ if (filterOption === "restrict") filterTCData = TCData.filter(o => cleanup(o.token.get(attribute)) === cleanup(filterValue)); //WORKS
+ if (filterOption === "include") filterTCData = TCData.filter(o => cleanup(o.token.get(attribute)).includes(cleanup(filterValue))); //WORKS
+ if (filterOption === "exclude") filterTCData = TCData.filter(o => !cleanup(o.token.get(attribute)).includes(cleanup(filterValue))); //WORKS
+ } else {
+ //log('WE ARE IN CHARACTER TERRITORY - ATTRIBUTE OR PROPERTY');
+ if (filterOption === "omit") filterTCData = TCData.filter(o => cleanup((o.character.get(attribute)) || cleanup(getAttrByName(o.character.get('_id'), attribute))) !== cleanup(filterValue));
+ if (filterOption === "restrict") filterTCData = TCData.filter(o => cleanup((o.character.get(attribute)) || cleanup(getAttrByName(o.character.get('_id'), attribute))) === cleanup(filterValue));
+ if (filterOption === "include") filterTCData = TCData.filter(o => cleanup((o.character.get(attribute)) || (cleanup(getAttrByName(o.character.get('_id'), attribute)))).includes(cleanup(filterValue)));
+ if (filterOption === "exclude") filterTCData = TCData.filter(o => !cleanup((o.character.get(attribute)) || !(cleanup(getAttrByName(o.character.get('_id'), attribute)))).includes(cleanup(filterValue)));
+ }
+ }
+ TCData = filterTCData
+ }
+ });
+
+ //log("Number of tokens after filtering = " + TCData.length);
+
+
+if (scrolling){
+ openReport = "
";
+}
+if (sidebar){
+ openReport = ((sidebar.includes('left')) ? openReport.replace('color: #000','color: #000; width:30%; float:left; margin-right:10px;') : openReport.replace('color: #000','color: #000; width:30%; float:right; margin-left:5px;'))
+}
+
+
+ if (TCData.length > 0) {
+ pageData = (getObj('page', TCData[0].token.get('_pageid')));
+ let pageName = pageData.get('name');
+ let countChar = "
(" + TCData.length + ")";
+
+ let header = openHeader + menuChar + repeatChar + countChar + TCData[0].token.get('name') + " - " + TCData[TCData.length - 1].token.get('name') + closeHeader;
+
+
+ currentLayer = currentLayer || TCData[0].token.get('layer');
+ switch (currentLayer) {
+ case "gmlayer":
+ layerChar = GMChar;
+ break;
+ case "map":
+ layerChar = MPChar;
+ break;
+ case "walls":
+ layerChar = DLChar;
+ break;
+ default:
+ layerChar = TKChar;
+ }
+ layerChar = ((isLayerButton) ? layerChar : '');
+
+ if (showPageInfo) {
+ pageInfo = "
";
+ } else {
+ pageInfo = ''
+ }
+
+
+
+ let attributes = []
+ if (undefined !== args[1]) {
+ let attributeLine = args[1];
+
+ switch (attributeLine) {
+ case "vision":
+
+
+ switch (sheet) {
+ case "D&D 5th Edition by Roll20":
+ attributes = ['t|has_bright_light_vision.|Vision', 't|has_night_vision,|Night|NV', 't|night_vision_distance|NV-dist', 'c|npc_senses|Perc.'];
+ break;
+ case "D&D 5E Shaped":
+ attributes = ['t|has_bright_light_vision.|Vision', 't|has_night_vision,|Night|NV', 't|night_vision_distance|NV-dist', 'c|senses_string|Perc.'];
+ break;
+ case "Pathfinder First Edition by Roll20":
+ attributes = ['t|has_bright_light_vision.|Vision', 't|has_night_vision,|Night|NV', 't|night_vision_distance|NV-dist', 'c|senses|Senses'];
+ break;
+ case "Pathfinder Second Edition by Roll20":
+ attributes = ['t|has_bright_light_vision.|Vision', 't|has_night_vision,|Night|NV', 't|night_vision_distance|NV-dist', 'c|repeating_senses_$0_sense|Senses'];
+ break;
+ default:
+ attributes = ['t|has_bright_light_vision.|Vision', 't|has_night_vision,|Night|NV', 't|night_vision_distance|NV-dist'];
+ }
+
+
+
+
+
+
+
+
+
+
+ break;
+ case "light":
+ attributes = ['t|emits_bright_light,|Bright|Bright', 't|bright_light_distance|distance', 't|emits_low_light,|Low', 't|low_light_distance|distance'];
+ break;
+ case "detail":
+
+ switch (sheet) {
+ case "D&D 5th Edition by Roll20":
+ attributes = ['c|strength.|St', 'c|dexterity.|Dx', 'c|constitution,|Cn', 't|bar1_value|hp', 'c|intelligence.|In', 'c|wisdom.|Ws', 'c|charisma,|Ch', 'c|ac|AC', 'c|npc_senses|Senses'];
+ break;
+ case "D&D 5E Shaped":
+ attributes = ['c|strength.|St', 'c|dexterity.|Dx', 'c|constitution|Cn', 't|bar1_value|hp', 'c|intelligence.|In', 'c|wisdom.|Ws', 'c|charisma,|Ch', 'c|ac|AC', 'c|npc_senses|Senses'];
+ break;
+ case "Pathfinder First Edition by Roll20":
+ attributes = ['c|strength,|Str', 'c|dexterity,|Dex ', 'c|constitution|Con', 'c|intelligence,|Int', 'c|wisdom,|Wis', 'c|charisma|Cha', 'c|ac.|AC', 'c|ac_touch.|t', 'c|ac_flatfooted.|ff', 't|bar1|hp', 'c|senses|Senses'];
+ break;
+ case "Pathfinder Second Edition by Roll20":
+ attributes = ['c|strength,|Str', 'c|dexterity,|Dex ', 'c|constitution.|Con', 't|bar1|hp', 'c|intelligence,|Int', 'c|wisdom,|Wis', 'c|charisma.|Cha', 'c|armor_class#|AC', 'c|repeating_senses_$0_sense|Senses'];
+ break;
+ default:
+ attributes = args[1].split(/\s+/)
+ }
+ keywords = keywords + " source|false ";
+ source = false;
+
+
+
+
+ break;
+ default:
+ attributes = args[1].split(/\s+/)
+ }
+ } else {
+ attributes[0] = "t|_id|token_ID"
+ }
+
+ //calls up non-report responses like help, menu and possibly config eventually
+ switch (attributes[0]) {
+ case "help":
+ help();
+ break;
+ case "menu":
+ menu();
+ break;
+ case "report":
+ readingReport()
+ break;
+ default:
+
+
+
+ //log('attributes = ' + JSON.stringify(attributes));
+ let attribute = []
+ let customTag = "";
+ let attributeValue = '';
+ let npcattributeValue = '';
+ let attributeList = [];
+ let name = "";
+ target = 'character';
+ let tId = "";
+ let cId = "";
+ let separator = "";
+
+ //### Sorting
+
+
+
+
+ let sorter = TCSort.identity;
+ switch (sortTerm) {
+ case "charName":
+ sorter = TCSort.charName;
+ break;
+ case "charNameI":
+ sorter = TCSort.charNameI;
+ break;
+ case "tokenName":
+ sorter = TCSort.tokenName;
+ break;
+ case "tokenNameI":
+ sorter = TCSort.tokenNameI;
+ break;
+ case "bar1":
+ sorter = TCSort.bar1;
+ break;
+ case "bar1I":
+ sorter = TCSort.bar1I;
+ break;
+ case "bar2":
+ sorter = TCSort.bar2;
+ break;
+ case "bar2I":
+ sorter = TCSort.bar2I;
+ break;
+ case "bar3":
+ sorter = TCSort.bar3;
+ break;
+ case "bar3I":
+ sorter = TCSort.bar3I;
+ break;
+ case "bar1_max":
+ sorter = TCSort.bar1_max;
+ break;
+ case "bar1_maxI":
+ sorter = TCSort.bar1_maxI;
+ break;
+ case "bar2_max":
+ sorter = TCSort.bar2_max;
+ break;
+ case "bar2_maxI":
+ sorter = TCSort.bar2_maxI;
+ break;
+ case "bar3_max":
+ sorter = TCSort.bar3_max;
+ break;
+ case "bar3_maxI":
+ sorter = TCSort.bar3_maxI;
+ break;
+ case "currentside":
+ sorter = TCSort.currentside;
+ break;
+ case "currentsideI":
+ sorter = TCSort.currentsideI;
+ break;
+ case "cr":
+ sorter = TCSort.cr;
+ break;
+ case "crI":
+ sorter = TCSort.crI;
+ break;
+ default:
+ sorter = sorter = TCSort.identity
+ }
+ TCData = TCData.sort(sorter);
+
+ TCData.forEach(tc => {
+ //log('tc = ' + JSON.stringify(tc));
+ //log('tc.token = ' + JSON.stringify(tc.token));
+
+ name = tc.token.get('name');
+ tId = tc.token.get('_id');
+ cId = tc.character.get('_id');
+
+
+ if (buttonCode === 'actions') {
+ buttonLine = '';
+ actionList = findObjs({
+ type: 'ability',
+ _characterid: cId
+ });
+ actionList.forEach(a => {
+ if (a.get('istokenaction')) {
+ actionName = a.get('name');
+ actionName = actionName.replace(/\s\(/g, "-");
+ actionName = actionName.replace(/\)/g, "");
+ actionId = a.get('_id');
+ if (actionName !== "Check" && actionName !== "Save") {
+ buttonLine = buttonLine + '[' + actionName + '](~' + cId + '|' + actionId + ') | ';
+ }
+
+ }
+ })
+ buttonLine = buttonLine.replace(/\|\s*$/, "");
+ }
+
+
+
+
+ if (buttonLine) {
+ newbuttonLine = buttonLine.replace(/!token-mod --/g, "!token-mod --ignore-selected --ids " + tId + " --")
+ .replace(/!pcnote/g, "!pcnote --id" + tId)
+ .replace(/!gmnote/g, "!gmnote --id" + tId)
+ .replace(/!selfnote/g, "!selfnote --id" + tId)
+ .replace(/!setattr/g, "!setattr --charid " + cId)
+ .replace(/!modattr/g, "!modattr --charid " + cId)
+ .replace(/!modbattr/g, "!modbattr --charid " + cId)
+ .replace(/(\[.*?\])(\(.*?\))+/g, "$2$1")
+ .replace(/(\()+/g, "
")
+ .replace(/(\])+/g, "")
+ .replace('Q{selected|token_name}', tc.token.get('name'))
+ .replace('A{selected|token_name}', tc.token.get('name'))
+ .replace(/\/n/g, '
')
+ .replace(/Q{/g, '?{')
+ //.replace(/P{/g, '%{')
+ //.replace(/tokID/g, tId)
+ //.replace(/charID/g, cId)
+ //.replace(/tokname/g, tc.token.get('name'))
+ //.replace(/charname/g, npcSwap(attribute, getAttrByName(tc.character.get('_id'), 'npc_name')))
+ .replace(/A{/g, '@{');
+ } else {
+ newbuttonLine = "";
+ }
+
+
+ // ######## report subheader
+ function specificLayer(id) {
+ if (allLayers) {
+ let layer = tc.token.get("layer");
+ layer = ((isLayerButton) ? layer : 'nobutton');
+ switch (layer) {
+ case "gmlayer":
+ return `
${GMChar}`;
+ break;
+ case "map":
+ return "
" + MPChar + "";
+ break;
+ case "walls":
+ return "
" + DLChar + "";
+ break;
+ case "nobutton":
+ return "";
+ break;
+ default:
+ return `
${TKChar}`;
+ }
+ } else {
+ return ""
+ }
+ }
+ let notesHandout = ((reportName) ? ` handout|${reportName}| ` : ``)
+ characterSheet = ((characterSheetLink) ? "
- " + tc.character.get('name') + "" : ""); //"
🖺"
+ characterSheet = ((characterSheetButton) ? "
" + linkBox + "" : characterSheet); //"
🖺"
+ printButton = ((isPrintbutton) ? "
w" : "");
+ tokenNotesButton = ((isTokenNotesButton) ? "
T" : "");
+ tokenNotesRow = ((isTokenNotesRow) ? "

" : "");
+ charNotesButton = ((isCharNotesButton) ? "
C" : "");
+ bioButton = ((isBioButton) ? "
B" : "");
+ avatarButton = ((isAvatarButton) ? "
A" : "");
+ imageButton = ((isImageButton) ? "
I " : "");
+ tooltipButton = ((isTooltipButton) ? "
tt" : "");
+ tokenImageButton = ((isTokenImageButton) ? "
L" : "");
+ //tokenImageButton = ((isTokenImageButton) ? "

" : "");
+
+
+
+ noteButtons = tokenNotesRow + tokenImageButton + tokenNotesButton + charNotesButton + bioButton + avatarButton + tooltipButton + imageButton;
+
+ if (compact === false) {
+ tokenGraphicHeight = 37;
+ switch (sheet) {
+ case "D&D 5th Edition by Roll20":
+ secondline = ((subTitle) ? "
" + ((getAttrByName(tc.character.get('_id'), 'npc_type')) ? getAttrByName(tc.character.get('_id'), 'npc_type') : getAttrByName(tc.character.get('_id'), 'class_display')) + '' : "");
+ break;
+ case "D&D 5E Shaped":
+ secondline = ((subTitle) ? "
" + ((getAttrByName(tc.character.get('_id'), 'is_npc') === '1') ? getAttrByName(tc.character.get('_id'), 'type') : getAttrByName(tc.character.get('_id'), 'class_and_level')) + '' : "");
+ break;
+ case "Pathfinder First Edition by Roll20":
+ secondline = ((subTitle) ? "
" + ((getAttrByName(tc.character.get('_id'), 'npc')) ? getAttrByName(tc.character.get('_id'), 'npc_type') : getAttrByName(tc.character.get('_id'), 'race') + ", " + getAttrByName(tc.character.get('_id'), 'class')) + '' : "");
+ break;
+ case "Pathfinder Second Edition by Roll20":
+ secondline = ((subTitle) ? "
" + ((getAttrByName(tc.character.get('_id'), 'npc_type')) ? getAttrByName(tc.character.get('_id'), 'npc_type') + ", " + getAttrByName(tc.character.get('_id'), 'traits') : getAttrByName(tc.character.get('_id'), 'class') + " - " + getAttrByName(tc.character.get('_id'), 'level') + ", background: " + getAttrByName(tc.character.get('_id'), 'background')) + '' : "");
+ break;
+ case "Other":
+ secondline = "";
+ break;
+ default:
+ secondline = ((subTitle) ? "
" + ((getAttrByName(tc.character.get('_id'), 'npc_type')) ? getAttrByName(tc.character.get('_id'), 'npc_type') : getAttrByName(tc.character.get('_id'), 'class_display')) + '' : "");
+ }
+ }
+ /*
+ switch (sheet) {
+ case 'Other':
+ break;
+ case 'Pathfinder Second Edition by Roll20':
+ break;
+ case 'D&D 5th Edition by Roll20':
+ break;
+ default:
+ break;
+ }
+ */
+
+
+ switch (sheet) {
+ case 'Other':
+ differentName = "";
+ break;
+ case 'Pathfinder First Edition by Roll20':
+ differentName = ((getAttrByName(cId, 'npc') !== 1) ? "
PC " : "");
+ break;
+ case 'Pathfinder Second Edition by Roll20':
+ differentName = ((getAttrByName(cId, 'sheet_type') !== "npc") ? " PC " : "");
+ break;
+ case 'D&D 5E Shaped':
+ differentName = ((characterSheetLink) ? ((tc.character.get('name') !== getAttrByName(cId, 'character_name')) ? " " + ((getAttrByName(cId, 'character_name') !== '') ? "NPC" : 'PC') + " " : "") : "");
+ break;
+ case 'D&D 5th Edition by Roll20':
+ differentName = ((characterSheetLink) ? ((tc.character.get('name') !== getAttrByName(cId, 'npc_name')) ? " " + ((getAttrByName(cId, 'npc_name') !== '') ? "NPC" : 'PC') + " " : "") : "");
+ break;
+ default:
+ differentName = "";
+ break;
+ }
+
+
+
+ if (allLayers === true) {
+ lines = lines + "" + printButton + noteButtons + "
" + specificLayer(tc.token.get('_id')) + "" + tc.token.get('name') + "" + differentName + characterSheet + secondline + "
";
+ } else {
+ lines = lines + "";
+ }
+
+
+ if (attributes[0].match(/^[t|c]\|/)) {
+ attributes.forEach(a => {
+ attribute = a.split(/\|/)[1];
+ //detmermine whether to have each attribute on a line or several across a line
+ separator = "
";
+
+ switch (attribute.charAt(attribute.length - 1)) {
+ case ",": //three spaces
+ attribute = attribute.substring(0, attribute.length - 1);
+ separator = " ";
+ break;
+ case ".": //three spaces
+ attribute = attribute.substring(0, attribute.length - 1);
+ separator = " | ";
+ break;
+ case "#": //horzontal space between rows
+ attribute = attribute.substring(0, attribute.length - 1);
+ separator = "";
+ break;
+ case "-":
+ attribute = attribute.substring(0, attribute.length - 1);
+ separator = "
";
+ //separator = "
";
+ break;
+ default:
+ attribute = attribute;
+ separator = "
";
+ }
+
+
+
+ //attribute = ((tc.character.get('npc')) === 1) ? attribute : npcSwap(attribute); //accounts for differently named attributesbetween pc and npc
+ switch (sheet) {
+ case "Pathfinder First Edition by Roll20": //three spaces
+ attribute = ((npcSubstitutions) ? npcSwap(attribute, ((getAttrByName(tc.character.get('_id'), 'npc')) === "1" ? "1" : "0")) : attribute); //accounts for differently named attributesbetween pc and npc
+ break;
+ case "Pathfinder Second Edition by Roll20": //three spaces
+ attribute = ((npcSubstitutions) ? npcSwap(attribute, ((getAttrByName(tc.character.get('_id'), 'sheet_type')) === "npc" ? "1" : "0")) : attribute); //accounts for differently named attributesbetween pc and npc
+ break;
+ case 'D&D 5th Edition by Roll20':
+ isNPC = getAttrByName(tc.character.get('_id'), 'npc');
+ isNPC = ((isNPC) ? isNPC.toString() : '1');
+ attribute = ((npcSubstitutions) ? npcSwap(attribute, isNPC) : attribute); //accounts for differently named attributesbetween pc and npc
+ break;
+ case 'D&D 5E Shaped':
+ isNPC = getAttrByName(tc.character.get('_id'), 'is_npc');
+ isNPC = ((isNPC) ? isNPC.toString() : '1');
+ attribute = ((npcSubstitutions) ? npcSwap(attribute, isNPC) : attribute); //accounts for differently named attributesbetween pc and npc
+ break;
+ default:
+ attribute = attribute; //accounts for differently named attributesbetween pc and npc
+ }
+ target = a.split(/\|/)[0];
+ customTag = a.split(/\|/)[2];
+
+
+ if (attribute === "bar1" || attribute === "bar2" || attribute === "bar3") {
+ value = tc.token.get(attribute + "_value") + "/" + tc.token.get(attribute + "_max")
+ } else {
+ if (target === 't') {
+ value = tc.token.get(attribute);
+ } else {
+
+ if (undefined === tc.character.get(attribute)) {
+ value = getAttrByName(cId, attribute);
+ } else {
+ value = tc.character.get(attribute);
+
+ }
+
+
+ }
+ }
+
+ if (source) {
+ charType = (target === "t") ? tokenChar + ' ' : characterChar + ' ';
+ } else {
+ charType = "";
+ }
+
+ // #########Corrects for status markers
+ if (attribute === "statusmarkers") {
+ value = value.replace(/::\d\d\d\d\d/g, "");
+ value = ((value.charAt(0) === ',') ? value.substring(1) : value);
+ }
+
+ // #########Corrects for token gmnotes
+ if (attribute.match(/gmnotes|bio|notes/)) {
+ try {
+ value = unescape(decodeUnicode(value));
+ value = value.replace(/\[.*?\]\((.*?\.(jpg|jpeg|png|gif))\)/g, `

`);
+ }
+ catch(err) {
+ value = value
+ }
+ }
+
+ // #########Corrects for status markers
+ if (attribute === "page" || attribute === "pagename") {
+ value = getObj('page', tc.token.get("_pageid")).get("name");
+ }
+
+ //Corrects for returns
+ if (undefined !== value && typeof value === "string") {
+ value = value.replace(/\n/g, '
')
+ }
+
+ //sets prefix for each attribute report
+ if (customTag) {
+ if (customTag !== "-") {
+ prefix = '
' + customTag + ': '
+ } else
+ prefix = ''
+ } else {
+ prefix = '
' + attribute + ': '
+ }
+ //############### TEST THIS FOR PROBLEMS ON MANY REPORT TYPES
+ if (hideEmpty && (value === '' || undefined === value)) { } else {
+ lines = lines + charType + prefix + value + separator;
+ }
+
+
+ //let attributeline = ((atrribute !== "-") ? attribute + ': ' : '') + value + separator;
+
+ let attributeline = attribute + ': ' + value + separator;
+ });
+ }
+ lines = lines + newbuttonLine;
+
+ });
+
+ lines = openReport + "
" + ((customTitle) ? openHeader + menuChar + repeatChar + countChar + customTitle + closeHeader : "") + ((showHeader) ? header + pageInfo : "") + "" + lines + ((showFooter) ? header + pageInfo : "") + closeReport;
+ }
+ if (lines) {
+ //L({ reportName });
+ if (reportName) {
+
+
+ let reportHandout = findObjs({
+ type: 'handout',
+ name: reportName
+ });
+ reportHandout = reportHandout ? reportHandout[0] : undefined;
+
+
+
+ if (!reportHandout) {
+ reportHandout = createObj('handout', {
+ name: reportName,
+ archived: false
+ });
+ let reportHandoutid = reportHandout.get("_id");
+ sendChat('Reporter', toWhom + openReport + `Reporter has created a handout named
${reportName}.
Click
here to open.` + closeReport, null, {
+ noarchive: true
+ });
+
+
+ }
+
+
+
+
+
+ if (reportHandout) {
+
+ if (reportHandout) {
+ reportHandout.get("notes", function (notes) {
+ //L({notes});
+ if (notes.includes('
')) {
+ notes = notes.split('
')[0] + '
';
+ } else {
+ notes = '
'
+ }
+
+ reportHandout.set("gmnotes", '')//+ ((displayNotes) ? "
" : ""))
+ reportHandout.set("notes", notes + lines + '')//+ ((displayNotes) ? "
" : ""))
+ });
+ }
+ } else {
+ sendChat('Reporter', toWhom + openReport + `No handout named ${reportName} was found.` + closeReport, null, {
+ noarchive: true
+ });
+
+ }
+ } else {
+
+ sendChat("Reporter", toWhom + lines + '' , null, {
+ noarchive: true
+ });
+ }
+
+
+
+ }
+ } else {
+ sendChat('Reporter', toWhom + baseOpenReport + `No viable tokens found.` + closeReport, null, {
+ noarchive: true
+ });
+
+
+
+ }
+ }
+
+ });
+
+ //##################################
+ //Ping
+ //##################################
+ on('chat:message', (msg) => {
+ if ('api' !== msg.type) {
+ return;
+ }
+
+ var cmdName = /!RPping[gm|all]/;
+ var msgTxt = msg.content;
+
+ //if (msg.type == "api" && msgTxt.match(cmdName) && playerIsGM(msg.playerid)) {
+ if (msg.type == "api" && msgTxt.match(cmdName)) {
+ let args = msg.content.split(/\s+/);
+ let token_ID = args[1];
+
+ token = getObj('graphic', token_ID);
+ if (token) {
+ page = getObj('page', token.get('_pageid'));
+ if (msgTxt.includes('RPpingall')) {
+ sendPing(token.get("left"), token.get("top"), page.get('_id'), msg.playerid, true);
+ } else {
+ sendPing(token.get("left"), token.get("top"), page.get('_id'), msg.playerid, true, msg.playerid);
+ }
+ } else {
+ sendChat('Reporter', '/w gm ' + openReport + `That token does not seem to exist. Perhaps it was deleted or the id is incorrect.` + closeReport, null, {
+ noarchive: true
+ });
+
+ }
+ };
+
+ });
+
+
+
+ //##################################
+ //page-mod Work in Progress
+ //##################################
+
+ on('chat:message', (msg) => {
+ if ('api' !== msg.type) {
+ return;
+ }
+
+ var cmdName = "!RPpage-mod";
+ var msgTxt = msg.content;
+ let pageAttribute = '';
+ let pageValue = '';
+ let pageData = getObj('page', getPageForPlayer(msg.playerid));
+ let lines = '';
+
+
+ const stringToBoolean = function (string) {
+ switch (string.toLowerCase().trim()) {
+ case "true":
+ case "yes":
+ case "1":
+ return true;
+ case "false":
+ case "no":
+ case "0":
+ case null:
+ return false;
+ default:
+ return Boolean(string);
+ }
+ }
+
+ if (msg.type == "api" && msgTxt.indexOf(cmdName) !== -1 && playerIsGM(msg.playerid)) {
+ let args = msg.content.split(/\s--/);
+ let commands = args[1].split(/\s+/);
+
+
+
+ commands.forEach(c => {
+ pageAttribute = c.split(/\|/)[0];
+ pageValue = c.split(/\|/)[1];
+ //#################### Handling cases ########################
+ //sendChat('Reporter', '/w gm pageAttribute is ' + pageAttribute + '
pageValue is' + pageValue);
+ if (pageAttribute === 'fog_opacity') {
+ if (pageValue >= 1 && pageValue <= 100) {
+ pageValue = pageValue / 100;
+ } else {
+ sendChat('Reporter', '/w gm ' + openReport + pageValue + ' is not a valid value for ' + pageAttribute + ' It has been set to 35%.' + closeReport, null, {
+ noarchive: true
+ });
+ // sendChat('Reporter', '/w gm ' + pageValue + ' is not a valid value for ' + pageAttribute + ' It has been set to 35');
+ pageValue = "0.35";
+ }
+ }
+
+ if (pageAttribute === 'daylightModeOpacity') {
+ if (pageValue >= 1 && pageValue <= 100) {
+ pageValue = pageValue / 100;
+ } else {
+ sendChat('Reporter', '/w gm ' + openReport + pageValue + ' is not a valid value for ' + pageAttribute + ' It has been set to 100%.' + closeReport, null, {
+ noarchive: true
+ });
+ pageValue = 1.0;
+ }
+ }
+
+
+ if (pageAttribute === 'dynamic_lighting_enabled') {
+ if (pageValue === "false" || pageValue === "true") {
+ stringToBoolean(pageValue);
+ //log('2 pageValue is now ' + pageValue);
+ } else {
+ sendChat('Reporter', '/w gm ' + openReport + pageValue + ' is not a valid value for ' + pageAttribute + ' It has been set to false.' + closeReport, null, {
+ noarchive: true
+ });
+ // sendChat('Reporter', '/w gm ' + pageValue + ' is not a valid value for ' + pageAttribute + ' It has been set to false.');
+ pageValue = false;
+ }
+ }
+
+ if (pageAttribute === 'daylight_mode_enabled') {
+ if (pageValue === "false" || pageValue === "true") {
+ (pageValue === 'true') ? true : false;
+ } else {
+ pageValue = false;
+ sendChat('Reporter', '/w gm ' + openReport + pageValue + ' is not a valid value for ' + pageAttribute + ' It has been set to false.' + closeReport, null, {
+ noarchive: true
+ });
+ }
+ }
+
+ //log(pageAttribute + ' is ' + pageData.get(pageAttribute));
+ //log(pageAttribute + ' requested' + pageValue);
+ if (pageValue === "false") {
+ pageData.set(pageAttribute, false);
+
+ } else {
+ pageData.set(pageAttribute, pageValue);
+ pageData.set('force_lighting_refresh', true);
+
+ }
+
+ //pageData.set(pageAttribute, pageValue);
+ //pageData.set(pageAttribute, pageValue)
+ //log(pageAttribute + ' is now ' + pageData.get(pageAttribute));
+ lines = lines + pageAttribute + ' has been changed to ' + (pageAttribute === "fog_opacity" ? pageValue * 100 + "%" : pageValue) + '
';
+
+ });
+
+ sendChat('Reporter', '/w gm ' + openReport + lines + '' + closeReport, null, {
+ noarchive: true
+ });
+
+ };
+ });
+ //log("-=> !RPpage-mod command loaded <=-")
+
+ //##################################
+ //EchoChat
+ //##################################
+
+
+ on('chat:message', (msg) => {
+ if ('api' !== msg.type) {
+ return;
+ }
+
+ var cmdName = "!RPechochat";
+ var msgTxt = msg.content;
+
+ if (msg.type == "api" && msgTxt.indexOf(cmdName) !== -1 && playerIsGM(msg.playerid)) {
+
+ let args = msg.content.split(/\s--/);
+ sendChat('echochat', '/w gm ' + args[1]);
+ };
+ });
+ //log("-=> !RPechochat command loaded (!RPechochat) <=-")
+
+
+
+ //##################################
+ //Changelayer
+ //##################################
+
+
+ on('chat:message', (msg) => {
+ if ('api' !== msg.type) {
+ return;
+ }
+
+ var cmdName = /!RPchangelayer/;
+ var msgTxt = msg.content;
+
+ if (msg.type == "api" && msgTxt.match(cmdName) && playerIsGM(msg.playerid)) {
+ let args = msg.content.split(/\s+/);
+ let token_ID = args[1];
+ //log('token id is ' + token_ID);
+ token = getObj('graphic', token_ID);
+
+
+ if (token.get('layer') === "gmlayer") {
+ token.set('layer', 'objects')
+ } else {
+ token.set('layer', 'gmlayer')
+ }
+
+
+ };
+
+ });
+ //log("-=> !RPchangelayer command loaded (!RPchangelayer [token_id]) <=-")
+
+});
+
+
+{ try { throw new Error(''); } catch (e) { API_Meta.Reporter.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.Reporter.offset); } }
diff --git a/Reporter/Reporter.js b/Reporter/Reporter.js
index 9fc3cb596c..1528ee176d 100644
--- a/Reporter/Reporter.js
+++ b/Reporter/Reporter.js
@@ -1,3 +1,7 @@
+//NEEDED CHANGES
+// https://app.roll20.net/private_message/m/6013942/reporter-api/#message-6014434
+// Referende the disabled Dev copy. I think I started making Jumpgate 5e2024 changes.
+
// Reporter
// Last Updated: 2021-03-26
// A script to report token and character calls in a list.
@@ -29,21 +33,22 @@ on('ready', function () {
on('ready', () => {
- const version = '1.1.8'; //version number set here
+ const version = '1.1.9'; //version number set here
log('-=> Reporter v' + version + ' is loaded. Internal commands of !RPping, !RPpage-mod, !RPechochat, and !RPchangelayer are used in code.');
//sendChat('Reporter', '/w gm Ready');
const L = (o) => Object.keys(o).forEach(k => log(`${k} is ${o[k]}`));
const tokenChar = '
T';
const characterChar = '
C';
- const GMChar = '
GM';
- const TKChar = '
TK';
- const DLChar = '
DL';
- const MPChar = '
MP';
+ const GMChar = '
GM';
+ const TKChar = '
TK';
+ const DLChar = '
DL';
+ const MPChar = '
MP';
const menuChar = `
l`;
const printChar = '
P';
const printButtonStyle = "'float:right; color: #000; font-weight:bold; color: white;text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000; font-family: pictos; border:none; background-color: transparent; padding:0px 2px; margin-right:3px !important'";
- const notesButtonStyle = "'float:right; color: white; font-size: smaller; border:none; background-color: #999; padding:0px 2px; margin-right:3px !important'";
+ const notesButtonStyle = "'float:right; color: white; font-size: smaller; font-weight:bold; border:none; background-color: #999; padding:0px 2px; margin-right:3px; border-radius:2px; !important'";
+ const rowButtonStyle = "'float:right; font-size: smaller; border:none; display:inline-block; overflow: hidden; !important'";
const buttonStyle = "'background-color: transparent;padding: 0px;color: #ce0f69;display: inline-block;border: none; !important'";
const headerButtonStyle = '"background-color: #ccc; padding: 0px 3px; border-radius:2px;color: black; display: inline-block;border: none; !important"';
const openHeader = ""
@@ -58,11 +63,34 @@ on('ready', () => {
return '
' + name + '';
}
- let actionButtons = [];
+function toDeci(fraction) {
+ if(undefined === fraction){return "0 or undefined"};
+ fraction = fraction.toString();
+ var result,wholeNum=0, frac, deci=0;
+ if(fraction.search('/') >=0){
+ if(fraction.search('-') >=0){
+ wholeNum = fraction.split('-');
+ frac = wholeNum[1];
+ wholeNum = parseInt(wholeNum,10);
+ }else{
+ frac = fraction;
+ }
+ if(fraction.search('/') >=0){
+ frac = frac.split('/');
+ deci = parseInt(frac[0], 10) / parseInt(frac[1], 10);
+ }
+ result = wholeNum+deci;
+ }else{
+ result = fraction
+ }
+ return result;
+}
+ let actionButtons = [];
+
L({ sheet });
@@ -337,8 +365,10 @@ on('ready', () => {
bar2_maxI: (a, b) => (a.token.get('bar2_max').toString().toLowerCase() > b.token.get('bar2_max').toString().toLowerCase() ? -1 : 0),
bar3_max: (a, b) => (a.token.get('bar3_max').toString().toLowerCase() < b.token.get('bar3_max').toString().toLowerCase() ? -1 : 0),
bar3_maxI: (a, b) => (a.token.get('bar3_max').toString().toLowerCase() > b.token.get('bar3_max').toString().toLowerCase() ? -1 : 0),
- cr: (a, b) => (eval(getAttrByName(a.character.get('_id'), "npc_challenge")) < eval(getAttrByName(b.character.get('_id'), "npc_challenge")) ? -1 : 0),
- crI: (a, b) => (eval(getAttrByName(a.character.get('_id'), "npc_challenge")) > eval(getAttrByName(b.character.get('_id'), "npc_challenge")) ? -1 : 0)
+ currentside: (a, b) => (a.token.get('currentSide').toString().toLowerCase()*1 < b.token.get('currentSide').toString().toLowerCase()*1 ? -1 : 0),
+ currentsideI: (a, b) => (a.token.get('currentSide').toString().toLowerCase()*1 > b.token.get('currentSide').toString().toLowerCase()*1 ? -1 : 0),
+ cr: (a, b) => toDeci((getAttrByName(a.character.get('_id'), "npc_challenge")) < toDeci(getAttrByName(b.character.get('_id'), "npc_challenge")) ? -1 : 0),
+ crI: (a, b) => toDeci((getAttrByName(a.character.get('_id'), "npc_challenge")) > toDeci(getAttrByName(b.character.get('_id'), "npc_challenge")) ? -1 : 0)
};
//cleanup(getAttrByName(o.character.get('_id'), "CR"))).toString().toLowerCase()
@@ -629,6 +659,7 @@ on('ready', () => {
source = ((keywords.includes("source|false")) ? false : true);
isPrintbutton = ((keywords.includes("printbutton|true")) ? true : false);
isTokenNotesButton = ((keywords.includes("tokennotesbutton|true")) ? true : false);
+ isTokenNotesRow = ((keywords.includes("tokennotesrow|true")) ? true : false);
isTokenImageButton = ((keywords.includes("tokenbutton|true")) ? true : false);
isCharNotesButton = ((keywords.includes("charnotesbutton|true")) ? true : false);
isBioButton = ((keywords.includes("biobutton|true")) ? true : false);
@@ -782,13 +813,9 @@ customTitle = keywords.match(/minimal\|.*?$/).toString().split("|")[1];
character: getObj('character', t.get('represents'))
}))
.filter(o => undefined !== o.character);
- //test for empty set, then for sheet type of first found sheet
- /* if (TCData.length > 0) {
- if (getAttrByName(TCData[0].character.get('_id'), 'text_proficiency')) {
- sheet = "Pathfinder Second Edition by Roll20";
- }
- }
- */
+
+
+
let filterTCData = TCData;
let newTCData = [];
@@ -833,6 +860,9 @@ customTitle = keywords.match(/minimal\|.*?$/).toString().split("|")[1];
}
});
+ //log("Number of tokens after filtering = " + TCData.length);
+
+
if (scrolling){
openReport = "
";
}
@@ -1025,7 +1055,13 @@ if (sidebar){
case "bar3_maxI":
sorter = TCSort.bar3_maxI;
break;
- case "cr":
+ case "currentside":
+ sorter = TCSort.currentside;
+ break;
+ case "currentsideI":
+ sorter = TCSort.currentsideI;
+ break;
+ case "cr":
sorter = TCSort.cr;
break;
case "crI":
@@ -1126,6 +1162,7 @@ if (sidebar){
characterSheet = ((characterSheetButton) ? "
" + linkBox + "" : characterSheet); //"
🖺"
printButton = ((isPrintbutton) ? "
w" : "");
tokenNotesButton = ((isTokenNotesButton) ? "
T" : "");
+ tokenNotesRow = ((isTokenNotesRow) ? "

" : "");
charNotesButton = ((isCharNotesButton) ? "
C" : "");
bioButton = ((isBioButton) ? "
B" : "");
avatarButton = ((isAvatarButton) ? "
A" : "");
@@ -1136,7 +1173,7 @@ if (sidebar){
- noteButtons = tokenImageButton + tokenNotesButton + charNotesButton + bioButton + avatarButton + tooltipButton + imageButton;
+ noteButtons = tokenNotesRow + tokenImageButton + tokenNotesButton + charNotesButton + bioButton + avatarButton + tooltipButton + imageButton;
if (compact === false) {
tokenGraphicHeight = 37;
@@ -1196,12 +1233,14 @@ if (sidebar){
}
+
if (allLayers === true) {
lines = lines + "
" + printButton + noteButtons + "
" + specificLayer(tc.token.get('_id')) + "" + tc.token.get('name') + "" + differentName + characterSheet + secondline + "
";
} else {
lines = lines + "
";
}
+
if (attributes[0].match(/^[t|c]\|/)) {
attributes.forEach(a => {
attribute = a.split(/\|/)[1];
@@ -1423,7 +1462,7 @@ if (sidebar){
if (msg.type == "api" && msgTxt.match(cmdName)) {
let args = msg.content.split(/\s+/);
let token_ID = args[1];
- //log('token id is ' + token_ID);
+
token = getObj('graphic', token_ID);
if (token) {
page = getObj('page', token.get('_pageid'));
@@ -1441,7 +1480,7 @@ if (sidebar){
};
});
- //log("-=> !RPping command loaded (!RPping) <=-")
+
//##################################
diff --git a/Reporter/script.json b/Reporter/script.json
index 0605837dd8..9faae88afe 100644
--- a/Reporter/script.json
+++ b/Reporter/script.json
@@ -1,7 +1,7 @@
{
"name": "Reporter",
"script": "Reporter.js",
- "version": "1.1.8",
+ "version": "1.1.9",
"description": "**Reporter** reads the tokens on the board that are associated with character sheets and builds a report of them in the chat or to a handout, returning selected values from the token settings or the character sheets they are associated with.\rReporter Reporter has specific support for the D&D 5th Edition by Roll20, D&D5E Shaped, Pathfinder First Edition by Roll20, and Pathfinder Second Edition by Roll20 sheet. There is an option to choose other for the sheet, which will disable the few sheet-specific shortcuts. It should work with most any sheet or no sheet at all. The first time you run the script, it will ask you to choose which sheet you are using. You can change this behavior with !report --config|sheet\rYou can either select a set of tokens to work with, or if you select no tokens, it will assume all tokens on the Object/Token layer. This behavior can be altered using keywords, described below. The basic syntax is:\r`!report --[queries] ---[buttonline] ----[keywords]`\r****\r### Queries\rQueries are constructed using\r`t|attribute` to poll a token attribute\r`c|attribute` to poll a character sheet attribute\r**Examples**\r`--t|name` would return a report of all selected token names\r`--c|strength` would return a report of all strength values on the character sheets of the selected tokens\rFor character sheets, the script will try to pull a value from the character journal first, and if that does not exist, the installed character sheet.\r### Dividers\rYou may not always want each attribute reported on its own line. You can add a code after the attribute name(not the alias) to use something either than a line return between attributes\r**comma (`,`)** Adds three non-breaking spaces between this attribute and the next, keeping them on the same line when possible.\r**period (`.`)** Adds a vertical pipe between this attribute and the next, keeping them on the same line when possible.\r**dash (`-`)** Adds a thin gray horizontal rule between this attribute and the next.\r**Hashtag (`#`)** Adds a bit of horizontal space between this attribute and the next.\r_Examples:_\r`!report --t|emits_bright_light,|Bright-Light t|bright_light_distance|Distance t|emits_low_light,|Low-Light t|low_light_distance|Distance ---light`\rwill return this Bright Light and Distance on the first line of each record amd Low-Light and its Distance on the second, instead of each value taking up its own line\r\r### Aliases\rThere are times in the report when you would not like `has_bright_light_vision` in the report. You can substitute an alias for the attribute name that will display in chat. For this, just add another pipe after the query and type an alias.\r\rFor example, if a token has 60 feet of Night Vision:\r`t|night_vision_distance`\rmight produce:\rT: night_vision_distance = 60\rbut\r`t|night_vision_distance?NV`\rwould yield:\rT: NV= 60\r\r### Buttonline\rThe buttonline is a string containing text and Ability or API Command buttons. These are formed using the normal syntax for such things with a few exceptions.\rIn order to keep the Roll20 parser from resolving queries and attribute calls before the script gets them, they need to be written slightly differently.\r_Examples:_\r**@{token|name}** is written as **A{token|name}**\r**?{question|default_answer}** is written as Q**{question|default_answer}**\r\rFurther, for a handful of scripts, the Reporter API will attempt to parse the code so that each buttonline refers to the specific token being reported on. Currently **Token Mod**, **ChatSetAttr**, and **Supernotes** are supported.\r\r### Filters\rThere are four types of operator.\r`+` only includes the token/character pairs that matches the query\r`-` excludes any token/character pair that exactly matches the query\r`~` only includes any character that is a partial match for the query\r`^` excludes any character that is a partial match for the query\rthus:\r`!report||-|c|name|Goblin` will return all tokens that are not represented by the Goblin character sheet.\r`!report||~|c|name|Goblin` will return any tokens that are represented by the Goblin or Hobgoblin character sheet.\r`!report||-|c|npc|1||+|t| has_night_vision|true` will exclude all NPCs (leaving only PCs), and then only return those that have nightvision set.\rFilters do not support an alias, because they are never displayed in the final report.\rFilters are executed sequentially, with each filter working on the result from the last, so some logic is required for best results.\rFilters are case insensitive.\rThere is as yet, no way to test for an empty, or undefined value, however, the keyword `hideempty|true` will cause the report to suppress the display of empty values.\r\r### Special Codes\rReporter contains a few special codes for common cases, to make macro writing easier. You can put thes in place of normal commands:\r`--vision` as the Query will replace any declared query line with one designed to report most vision situations. It will give values for whether the token has sight, night vision and what the distance of any night vision is.\r`---vision` as the Buttonline will replace any declared button line with a buttonline designed to handle most cases of vision and darkvision.\r`--light` as the Query will replace any declared query line with one designed to report most lighting situations. It will give values for the amount of light, distance and what type.\r`---light` as the Buttonline will replace any declared button line with a buttonline designed to handle most cases of lighting.\r`---actions` as the Buttonline will replace any declared button line with a buttonline made up of the token action buttons associate with the character. This is designed for synergy with the Token Action Maker script, but is not essential. Not that the token actions created by this command cannot contain roll templates and will not convert the {selected|commandname} structure where it might appear in an ability. This requires very careful parsing and is best avoided. It should work flawlessly with Token Action Maker commands, with the exception of the *Check* and *Save* buttons (which it will skip), for the reasons just mentioned.\r\r### Keywords\rkeywords change the overall appearance or scope of the report. They are separated from the rest of the report by four dashes and must come at the end.\r`layer|[gmlayer|objects|map|walls|tracker|all]` will constrain the report to a particular layer or all layers at once, so long as no tokens are selected. If any tokens are selected, Reporter will default to the layer the selected tokens are on. This makes it easier for instance to check the vision settings of tokens on the token layer and the gmlayer simultaneously, or to ping pull to note tokens on the gm layer without switching manually to that layer. \rIf the layer keyword all is used the report will be on all token/character pairs on all layers. In this case, a layer character will appear on each subhead line of the report to let you know which layer the token is on. \rIf the layer keyword tracker is used the report will be on all token/character pairs on the Turn Tracker as if it were a layer. In this case, a layer character will appear on each subhead line of the report to let you know which layer the token is on. If you click on the layer token, it will switch the token from the GM/Notes layer to the Token/Objects layer and back.\r`compact|[true|false]` _(default=false):_ The compact mode shows the token image at half size, and eliminates the second line of the report subhead, since it is not always desired. You may have a very large report you want to see better, or you may be using a sheet that does not support the default values. Currently the second line of the subhead only references the _D&D 5th Edition by Roll20_ and _Pathfinder Second Edition by Roll20_ Sheets. \r`showheader|[true|false]` _(default=true):_ This will control whether the header will display at the top of the report. \r`showfooter|[true|false]` _(default=true):_ This will control whether the footer will display at the bottom of the report. \r`printbutton|[true|false]` _(default=true):_ This will control whether the print button will display on each line of the report. \r`notesbutton|[true|false]` _(default=false):_ This will control whether a notes button will display on each line of the report. This notes button will return the token notes for the token on that line. The visibility of the notes button is controlled by the visibility keyword. If the visibility is *gm*, it will use a !gmnote command, if the If the visibility is *whisper*, it will use a !selftnote command, and if the visibility is *all*, it will use a !pcnote command. \r`visibility|[gm|whisper|all]` _(default=gm):_ This will determine how the report is presented. *gm* is whispered to the gm, *whisper* is whispered to the user who sent the command, *all* is posted openly for all to see. \r`showfooter|[true|false]` _(default=true):_ This will control whether the footer will display at the bottom of the report. \r`source|[true|false]` _(default=true):_ if source is set to false, the C and T characters that show whether an attribute comes fromthe token or the sheet will not be displayed. Use this is they are a distraction. \r`charactersheetlink|[true|false]` _(default=true):_ if this keyword is set to false, the link to open the token*s corresponding character sheet will not display \r`subtitle|[true|false]` _(default=true):_ if this keyword is set to false, the line directly below the character name will not display. (This is also the default in Compact mode). This may be desirable if not using the _D&D 5th Edition by Roll20_ or _Pathfinder Second Edition by Roll20_ Sheets. \r`ignoreselected|[true|false]` _(default=false):_ if this keyword is set to true, the search will not be preset to whichever tokens are selected. The report will run as if no tokens were selected, following whatever layer criteria might have been specified. \r`npcsubstitutions[true|false]` _(default=true):_ if this keyword is set to false, the script will not automatically substitute npc attributes for their PC counterparts (ex: npc_senses for passive_wisdom).This is good for sheets that are not the _D&D 5th Edition by Roll20_ or _Pathfinder Second Edition by Roll20_ Sheets. \r`sort|attribute` _(default is the raw order):_ This keyword will sort the final list. Most of the sorts are confined to the token attributes, since they require internal code and if they refer to a sheet may return poor or no results if the sheet does not have the proper attributes. Currently the following values can be sorted on: \r- charName: character name. Sheet must have a *name* attribute.\r- charNameI: character name, inverse order. Sheet must have a *name* attribute.\r- tokenName: token name\r- tokenNameI: token name, inverse order.\r- bar1: token bar1 value\r- bar1I: token bar1 value, inverse order.\r- bar2: token bar2 value\r- bar2I: token bar2 value, inverse order.\r- bar3: token bar3 value\r- bar3I: token bar3 value, inverse order.\r- cr - Challenge Rating. D&D 5th Edition by Roll20 Sheet only\r- crI - Challenge Rating, inverse order. D&D 5th Edition by Roll20 Sheet only\r`title|Title|` If this is present in the keywords, the string in between pipes will be placed at the top of the report. If you only want the custom title to display, be sure turn off the header with showheader|false.\r`handout|Handoutname|` If this is present in the keywords, the report will be sent to a handout instead of chat. This can allow a report to remain usable without scrolling through the chat. It can also be used as a sort of floating palette. Reports in handouts can be updated. Running the macro again will regenerate the table, as will pressing the Repeat button. The string in between pipes will be used as the name of the report handout. If no handout by that name exists, Reporter will create one and post a link in chat to open it. The title must be placed between two pipes. handout|My Handout| will work. handout|My Handout will break.\rA report Handout automatically creates a horizontal rule at the top of the handout. Anything typed manually above that rule will be persistent. Reporter will not overwrite it. You can use this area to create Journal Command Buttons to generate new reports or to give some context to the existing report. All updates are live.\r**Supernotes Buttons**\rThese are small buttons that will appear on each line of the report that call up Supernotes commands. These buttons require Supernotes to be installed (Available from the Roll20 One Click installer). If Supernotes is not installed, the buttons will still display but will have no effect. If the report is in the Chat tab, the notes will display in the chat tab, and if the report is set to be in a handout, the notes will in the handout, directly below the report. This can be used to create a handout that can run a report and display notes below. An example use could be a handout that can read map pins and display the notes for each map pin, making an interactive city guide. \r`tokennotesbutton|[true|false]` _(default=false):_ If this keyword is set to true, the report will place a small shortcut button to return the contents of the reported tokens GM Notes field.\r`charnotesbutton|[true|false]` _(default=false):_ If this keyword is set to true, the report will place a small shortcut button to return the contents of the GM Notes field of the character assigned to the reported token.\r`biobutton|[true|false]` _(default=false):_ If this keyword is set to true, the report will place a small shortcut button to return the contents of the Bio Notes field of the character assigned to the reported token.\r`avatarbutton|[true|false]` _(default=false):_ If this keyword is set to true, the report will place a small shortcut button to return the Avatar of the character assigned to the reported token.\r`tooltipbutton|[true|false]` _(default=false):_ If this keyword is set to true, the report will place a small shortcut button to return contents of the reported tokens Tooltips field.\r`imagebutton|[true|false]` _(default=false):_ If this keyword is set to true, the report will place a small shortcut button to return images from the Bio field of the character assigned to the reported token.\rSee this thread in the Roll20 Forums for more details - [Reporter Feedback thread](https://app.roll20.net/forum/post/10381135/script-reporter-1-dot-x)",
"authors": "Keith Curtis",
"roll20userid": "162065",
@@ -9,5 +9,5 @@
"ability.*": "read, write"
},
"conflicts": [],
- "previousversions": ["1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7"]
+ "previousversions": ["1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.5","1.1.6","1.1.7","1.1.8"]
}