Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Simple If Then Loot Query

Good day, I am trying to create a very rudimentary looting system for my players. Namely, I want them to be able to use a targetable macro to pull up a simple list in chat of what loot that enemy has. The list will come from an ability that I will add to each npc with a listing of their loot. It should look something like %{target|loot}. However, I want to make it so that it will not work on anything that hasn't been defeated/killed/knocked out, etc. Specifically, I want it so that if the enemy has 0 HP, it works and they can see the loot. If the enemy has 1 or greater HP, it will not work. Ideally, I would like it to instead output a message such as "That enemy cannot be looted yet" or something along those lines. Bonus points for multiple options, so that I can include different messages for different amounts. However, simply not working or giving a generic error message would also work. Does anyone have any ideas on how I might create this sort of if then system, either through macros or with the API? Mathematically I can already do this, using a simple formula that returns 0 if they have 0 HP or 1 if they have any other amount of HP. However, I cannot think of any way to translate that into the actual effect that I want because of the way the system does its order of operations. I also do not know of an API script that does this, and do not know how to write one for myself. Thank you very much for any assistance.
1544412877

Edited 1544412944
Bast L.
API Scripter
Edit: The attribute I added to the NPC was called "Loot" Ok, just did quick testing, but it looks ok: Script: on ( "ready" , function () { on ( "chat:message" , function ( msg ) { if ( msg . type === "api" && msg . content . split ( '--' )[ 0 ] === "!Loot " ) { let parts = msg . content . split ( '--' ); if ( parts [ 1 ] && parts [ 2 ]) { let greenVal = findObjs ({ _type: 'graphic' , id: parts [ 1 ]})[ 0 ]. get ( "bar1_value" ); if ( greenVal > 0 ) { sendChat ( '' , "Target is not yet dead" ); } else { sendChat ( '' , "Target had the following items: " + parts [ 2 ]); } } } }); }); Macro: !Loot --@{target|foe|token_id}--@{target|foe|Loot}
1544455842

Edited 1544455880
GiGs
Pro
Sheet Author
API Scripter
You can simplify the macro a bit by making the script a touch more complex. In this version you just supply the token_id, and getting the loot attribute in the script itself, so call it with just:  !Loot --@{target|token_id} on("ready", function() { const LOOTNAME = "Loot"; on("chat:message", function (msg) { if (msg.type === "api" && msg.content.split('--')[0] === "!Loot ") { let parts = msg.content.split('--'); let token = findObjs({_type: 'graphic', id: parts[1].trim()}); if(!token) { // send error report return; } let character = getObj('character', token.get('represents')); if(!character) { // send error report return; } let greenVal = token.get("bar1_value"); if (greenVal > 0) { sendChat('',"Target is not yet dead"); } else { let loot = getAttrByName(character.id, LOOTNAME, 'current'); sendChat('', "Target had the following items: " + loot); } } }); }); While writing this, though, it occurred to me you often have multiple tokens using the same character (orc 1, orc 2, orc 3, etc) Do you want them all to have the same treasure? Two ways that occur to me to handle this: Token GM Notes: Store the treasure in the token's GM Notes (not character - tokens have their own GM notes section), and print that out. That has the downside of needing to edit every individual token which is a pain Random Table: Use the loot attribute to store a table: something like a list separated by '--' or some other character that isnt going to crop up in the text, and extract that in the script. Split it into an array on the separator, then use randomIntenger(array.length -1) to select the treasure item. If the loot attribute has a single item, this will just return it. So for this version, change the let loot= section to: let loottable = getAttrByName(character.id, LOOTNAME, 'current').split('--);                 let loot = loottable[randomInteger(loottable.length -1)]; sendChat('', "Target had the following items: " + loot);
Thank you both for the assistance. The second one I couldn't get to work, as it just errored out on me when I used it. Something about token.get not being valid or not existing. The first one, on the other hand, did work. While it does work, it has a couple of minor problems. I don't know if these actually have to do with that script, or if they are API limitations in general. First, it doesn't allow for rolling within the results. So, if the loot stat that the target has contains [[1d100]] gp, it won't roll it. It just comes out as $[[0]] gp. Second, I can't use multiple lines in the results. If the loot has the following: 100 gp Enchanted Sword +1 Leather Armor then it simply puts the first line in the results as output by the API (as in, without my name attached), while all the other lines appear in a separate text output with my name as though I had typed them into chat as normal. Third, there doesn't seem to be a way for me to output the text with templates from a character sheet. I am using 5e OGL, and like to use its templates even for custom roles to keep the chat nice and neat. However, that does not seem to work here. If you have any ideas on how to solve these issues, that would be helpful. Even if not, the script does what I need it to do, and I can work around its limitations. Thank you very much.
1545114711

Edited 1545114960
GiGs
Pro
Sheet Author
API Scripter
I'm pretty sure the reason mine failed was because of this line let token = findObjs({_type: 'graphic', id: parts[1].trim()}); It should be  let token = findObjs({_type: 'graphic', id: parts[1].trim()})[0]; Oops. It is totally possible to format your output to go with a rolltemplate, but I dont know the 5e sheet rolltemplate syntax, nor do i know how you are storing the loot value so I can't attempt to add that without more information. Can you give two pieces of information: A couple of examples of loot attributes (e.g. the contents of the loot attribute from a couple of characters) How you'd like printed out, using a rollttemplate. With those we should be able to format a script to handle this. There is a fix for the $[[0]] issue, I'll post an updated version later.
1545128969

Edited 1545129548
GiGs
Pro
Sheet Author
API Scripter
Here's a script that works for me, using the Default Template. Show me what the template you are using requires, and I can tweak it. on("ready", function()  {     const LOOTNAME = 'Loot';     const HPBAR = 1;     const DIVIDER = '|';     on("chat:message", function (msg) {         if (msg.type === "api" && msg.content.match(`!${LOOTNAME}`) ) {             let id = msg.content.replace(`!${LOOTNAME}`,'').trim();             let token = findObjs({_type: 'graphic', id: id})[0];             if(!token) {                 sendChat(LOOTNAME,"/w GM Token Not Found");                 return;             }             let character = getObj('character', token.get('represents'));                         if(!character) {                 sendChat(LOOTNAME,"/w GM Character Not Found");                 return;             }             let greenVal = token.get(`bar${HPBAR}_value`);             if (isNaN(greenVal)) {                 sendChat(LOOTNAME,"/w GM Target's Bar is not a number");             } else if (greenVal > 0) {                 sendChat(LOOTNAME,"Target is not yet dead");             } else {                 let loot = getAttrByName(character.id, LOOTNAME, 'current');                 if(!loot) {                     sendChat(LOOTNAME,"/w GM Loot Not Found.");                 } else {                         let output = `&{template:default} {{name=${token.get('name')}'s Treasure}}`;                     let items = loot.split(DIVIDER);                     let i = 1;                     items.forEach(item => {                         output += `{{Item ${i}=${item}}}`;                         i++;                     });                     sendChat(LOOTNAME, output);                 }             }         }     }); }); Launch it with !Loot @{selected|token_id} (You don't need --, and it will fail if you do). i tried it with the following Loot attribute: Some stuff |and [[2d6]] more stuff  |and [[3d6]] more items Note that the vertical pipe | is used to create a new row. The output looks like The inline rolls worked fine. You can change the character used as a divider, and the name of the loot attribute (and script), and the bar used for HP. See the top 3 const references in the script. If you need to change this for a different rolltemplate, let me know what example rolltemple output you need.
I tested out your new one and it seems to work. For this I am trying to use the @{template|traits} from the 5E OGL sheet. I was able to find where you put the template in the script and tried changing it, which partially worked. However, I need the actual output to contain {{description= }} for the actual loot. It just includes the "Name's Treasure" bit otherwise. I couldn't determine how to make it output that correctly with the script. A typical input using that would look like: &{template:traits} {{name=@{target|token_name}'s Loot}} {{description=%{target|Loot}}} Also, is it possible to make it read from an ability, rather than an attribute? 5E OGL contains a ridiculous amount of attributes, and it's easier to type into and format in an ability regardless. Lastly, it is meant to be turned into a macro that players can use to target NPCs, even if they don't control the targeted token. So, working off a selected token defeats a large part of the purpose. Thank you very much.
1545170787
GiGs
Pro
Sheet Author
API Scripter
Selected and target are interchangeable - if you can use one, you can always use the other. So you can launch it with !Loot @{target|token_id} without issue. That rolltemplate should be easy to include. Does {{description=}} in that template support multiline outputs and paragraphs? What character would you use to signal a line break? Reading from an ability is possible, I'll need to refresh my memory how to do it once I've woken up and have some time :)
Yes, the description allows multiline and paragraphs. I can simply use enter as though I were typing regularly and it goes to the next line. So, I could do this: {{description=100 gp 1 longsword leather armor}} and it would appear on three separate lines within the same output.
1545180869

Edited 1545181796
GiGs
Pro
Sheet Author
API Scripter
Okay here's a version I think will work for what you need. It took a little bit of circuitous logic to get there. Use an Ability named Loot. Enter the treasure in there like 100 gp 1 longsword leather armor Or maybe [[3d6]] gp 1 longsword leather armor Launch the script with either  !Loot @{target|token_id} or !Loot @{selected|token_id} Here's the script: /* !Loot @{target|token_id} or !Loot @{selected|token_id} You must have an Ability on the character sheet named Loot. This will be printed out, with any inline rolls rolled, and linebreaks maintained. If you have items separated by a pipe (|) only one will be returned. This lets you include a simple randomizer. */ var loot = loot || (function () {     'use strict';     const version = '0.2',         COMMAND = 'Loot', // this is the name you must use for the Ability, make sure capitalisation matches.         HPBAR = 1,  // this is the bar used for tracking HP         scriptName = 'Loot',         lastUpdate = 1545180290523,          divider = ' -|- ', // do not use same as newline         newline = '--',         random = '|',                  checkInstall = function () {             log('--| ' + scriptName + ' v' + version + ' |--   [' + (new Date(lastUpdate)) + ']');         },         processInlinerolls = function (msg) {             if (_.has(msg, 'inlinerolls')) {                 return _.chain(msg.inlinerolls)                     .reduce(function(previous, current, index) {                         previous['$[[' + index + ']]'] = current.results.total || 0;                         return previous;                     },{})                     .reduce(function(previous, current, index) {                         return previous.replace(index, current);                     }, msg.content)                     .value();             } else {                 return msg.content;             }         },                  getSpeaker = function(msg) {             let characters = findObjs({_type: 'character'});             let speaking;             characters.forEach(function(chr) { if(chr.get('name') == msg.who) speaking = chr; });                  let speaker = speaking ? 'character|'+speaking.id : 'player|'+msg.playerid;             return speaker;         },         handleInput = function (msg) {             if ('api' === msg.type && msg.content.match(`!format${COMMAND} `)) {                 let args = processInlinerolls(msg).split(divider); //);                 let lootBase = args[2].split(newline);                 let loot = [], i = 0;                 lootBase.forEach(row => {                     let item = row.split(random);                     loot[i] = item.length === 1 ? item : item[randomInteger(item.length -1)];                     i ++;                 });                 let output = `&{template:traits} {{name=${args[1].trim()}'s Treasure}} {{description=${loot.join('\n')}}}`;                 let speaker = args[3];                 sendChat(speaker,output);             } else if ('api' === msg.type && msg.content.match(`!${COMMAND} `)) {                  let id = msg.content.replace(`!${COMMAND}`,'').trim();                 let token = findObjs({_type: 'graphic', id: id})[0];                                  if(!token) {                     sendChat(COMMAND,'/w GM Token Not Found');                     return;                 }                 let character = getObj('character', token.get('represents'));                             if(!character) {                     sendChat(COMMAND,'/w GM Character Not Found');                     return;                 }                 let greenVal = token.get(`bar${HPBAR}_value`);                 if (isNaN(greenVal)) {                     sendChat(COMMAND,"/w GM Target's Bar is not a number");                 } else if (greenVal > 0) {                     sendChat(COMMAND,'Target is not yet dead');                 } else {                     //let loot = getAttrByName(character.id, COMMAND, 'current');                     let loot = findObjs({_type: 'ability', characterid: character.id, name: COMMAND})[0];                     if(!loot) {                         sendChat(COMMAND,'/w GM Loot Not Found.');                     } else {                           let speaker = getSpeaker(msg);                         let name = token.get('name');                         let action = loot.get('action').toString();                         let output = action.split('\n').join(newline);                         sendChat(COMMAND, `!format${COMMAND} ${divider} ${name} ${divider} ${output} ${divider} ${speaker}`); //                     }                 }                              }          },         registerEventHandlers = function () {             on('chat:message', handleInput);         };     return {         CheckInstall: checkInstall,         RegisterEventHandlers: registerEventHandlers     }; }()); on('ready', function () {     'use strict';     loot.CheckInstall();     loot.RegisterEventHandlers(); });
1545182008
GiGs
Pro
Sheet Author
API Scripter
I quickly added a rudimentary table roller. If you have in your Loot ability several items separated by a pipe, it will pick one at random, each having an equal chance. So if you had the following Loot Ability: 100 gp 1 longsword|1 longsword|1 shortsword|1 bow, [[4d6 arrows]] leather armor|chainmail It would have a 50% chance of giving leather armour, and the same chance of chainmail. It would also give either a longsword, shortsword, or bow + arrows - with longswords being twice as common as the others (which is why its listed twice). This feature doesnt substitute for a proper treasure designer, but it was easy to add so i added it :) The script in the previous post has been updated.
My few minor tests seem to show that working splendidly. The table roller makes me have to use HTML replacements for the | within any sort of ability call that I include within the loot, but that is easy and not a problem. You have been quite helpful, and I hesitate to ask for more, but would it be possible to ask for one additional feature to be added to it? The only thing left is a way to prevent players from repeatedly looting the same thing. Is it possible to have the script add a symbol to the token that was looted successfully, to show that it has been looted? Additionally, the script would check for that symbol when it rolls, outputting a message about the target having already been looted if they tried again. I don't know if it's easier or harder to do a symbol or a color, but it could be either. For example, the trophy symbol or the color yellow (neither of which I am using for anything at the moment). If that is not possible, that's alright. You have already done more than enough, and quite a bit more than I expected when I started this thread.
1545255066
GiGs
Pro
Sheet Author
API Scripter
That's definitely possible. Are you talking about a status marker to put on the token? If so, which would you like to use?
Yes, a status marker on the token itself (not a stat or anything related to the source character, since I have multiple tokens connected to one character). Just because I don't use it for anything, the trophy symbol is probably a good one. It is in the second row from the bottom when selecting a token.
1545264913

Edited 1545273402
GiGs
Pro
Sheet Author
API Scripter
The trophy does look like a good choice. Here you go! I also made some tweaks to the messages that pop up when you try to loot an invalid token, to make it clearer which token is invalid. /* !Loot @{target|token_id} or !Loot @{selected|token_id} You must have an Ability on the character sheet named Loot. This will be printed out, will roll any inline rolls, and preserve paragraphs. */ var loot = loot || (function () {     'use strict';     const version = '0.3',         COMMAND = 'Loot', // this is the name you must use for the Ability, make sure capitalisation matches.         HPBAR = 1,  // this is the bar used for tracking HP         MARKER = 'trophy', // set the status marker used to show if treasure has been rolled for this token.         scriptName = 'Loot',         lastUpdate = 1545273360606,          divider = ' -|- ', // do not use same as newline         newline = '--',         random = '|',                  checkInstall = function () {             log('--| ' + scriptName + ' v' + version + ' |--   [' + (new Date(lastUpdate)) + ']');         },         processInlinerolls = function (msg) {             if (_.has(msg, 'inlinerolls')) {                 return _.chain(msg.inlinerolls)                     .reduce(function(previous, current, index) {                         previous['$[[' + index + ']]'] = current.results.total || 0;                         return previous;                     },{})                     .reduce(function(previous, current, index) {                         return previous.replace(index, current);                     }, msg.content)                     .value();             } else {                 return msg.content;             }         },                  getSpeaker = function(msg) {             let characters = findObjs({_type: 'character'});             let speaking;             characters.forEach(function(chr) { if(chr.get('name') == msg.who) speaking = chr; });                  let speaker = speaking ? 'character|'+speaking.id : 'player|'+msg.playerid;             return speaker;         },         handleInput = function (msg) {             if ('api' === msg.type && msg.content.match(`!format${COMMAND} `)) {                 let args = processInlinerolls(msg).split(divider); //);                 let lootBase = args[2].split(newline);                 let loot = [], i = 0;                 lootBase.forEach(row => {                     let item = row.split(random);                     loot[i] = item.length === 1 ? item : item[randomInteger(item.length -1)];                     i ++;                 });                 let output = `&{template:traits} {{name=${args[1].trim()}'s Treasure}} {{description=${loot.join('\n')}}}`;                 let speaker = args[3];                 sendChat(speaker,output);             } else if ('api' === msg.type && msg.content.match(`!${COMMAND} `)) {                  let id = msg.content.replace(`!${COMMAND}`,'').trim();                 let token = findObjs({_type: 'graphic', id: id})[0];                 let tname = token.get('name');                 if(!token) {                     sendChat(COMMAND,'/w GM Token Not Found');                     return;                 }                 if(token.get('status_' + MARKER)) {                     sendChat(COMMAND,`${tname} has already been looted.`);                     return;                 }                 let character = getObj('character', token.get('represents'));                             if(!character) {                     sendChat(COMMAND,`/w GM Character Not Found for token ${tname}.`);                     return;                 }                 let greenVal = token.get(`bar${HPBAR}_value`);                 if (isNaN(greenVal)) {                     sendChat(COMMAND,`/w GM ${tname}'s Bar is not a number`);                 } else if (greenVal > 0) {                     sendChat(COMMAND,`${tname} is not yet defeated.`);                 } else {                     //let loot = getAttrByName(character.id, COMMAND, 'current');                     let loot = findObjs({_type: 'ability', characterid: character.id, name: COMMAND})[0];                     if(!loot) {                         sendChat(COMMAND,`/w GM Loot Not Found for ${tname}'s token.`);                     } else {                           let speaker = getSpeaker(msg);                         let action = loot.get('action').toString();                         let output = action.split('\n').join(newline);                         token.set('status_' + MARKER,true);                         sendChat(COMMAND, `!format${COMMAND} ${divider} ${tname} ${divider} ${output} ${divider} ${speaker}`); //                     }                 }                              }          },         registerEventHandlers = function () {             on('chat:message', handleInput);         };     return {         CheckInstall: checkInstall,         RegisterEventHandlers: registerEventHandlers     }; }()); on('ready', function () {     'use strict';     loot.CheckInstall();     loot.RegisterEventHandlers(); });
Thank you very much. It works very well in my tests so far. Now I just have to actually go through all of my NPCs and generic mobs and add the loot. I truly appreciate the work. You went far above and beyond what I was aiming for when I started this. So, once again, thank you.
1546109127
GiGs
Pro
Sheet Author
API Scripter
That's good to hear, thanks!