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

Know Your Enemy macro?

Hey all, I just had a character level to 7 as a Fighter/Battle Master in 5E. The "Know Your Enemy" ability states Starting at 7th level, if you spend at least 1 minute observing or interacting with another creature outside combat, you can learn certain information about its capabilities compared to your own. The DM tells you if the creature is your equal, superior, or inferior in regard to two of the following characteristics of your choice: Strength score Dexterity score Constitution score Armor Class Current hit points Total class levels, if any Fighter class levels, if any I was wondering if anyone knows of an existing macro, or even an approach to writing one that doesn't require the API? It seems like the ability to check two of a group is not going to happen in a single macro. The only feasible thing that comes to mind is something like a Chat Menu that has "I'd like to know and all the choices of listed, and the player would click two of them, sending two separate messages. Each of these messages would have the response of 'equal', 'superior', 'inferior', (and maybe 'N/A' for the class levels). Any other thoughts on it? Thanks for any pointers or assistance, M
1611036840

Edited 1611036903
It's not going to be possible to create a macro that outputs 'equal', 'superior', or 'inferior', but you could create one that puts the character stats side-by-side with the NPC stats. Most NPCs aren't going to have Class Levels or Fighter Class Levels, so here's one that just shows STR, DEX, CON, AC, and HP (for the D&D 5e by Roll20 character sheet): /w gm &{template:npcaction} {{rname=Know Your Enemy}} {{name=@{selected|token_name} vs. @{target|Enemy|token_name}}} {{description=Stat  PC   NPC **STR:** @{selected|strength} | @{target|Enemy|strength} **DEX:** @{selected|dexterity} | @{target|Enemy|dexterity} **CON:** @{selected|constitution} | @{target|Enemy|constitution} **AC:**  @{selected|ac} | @{target|Enemy|npc_ac} **HP:**  @{selected|hp} | @{target|Enemy|bar1}}} For the hp, you may need to use a different bar reference (bar1 in the example above). And it can get prettied up with some Style Injection if you really want to jump into the deep end.
1611066906
David M.
Pro
API Scripter
Yeah, without api access, something like what Jarren proposed is going to be your best bet. Note: there is a hacky way to give you the output without manual comparison, with some substantial caveats including a fair amount of prep work. 1) You could create a set of rollable tables named ...Compare-1, Compare0, Compare1, etc. Each table would have one item (either Inferior, Equal, or Superior). 2) You would modify Jarren's macro to include inline rolls that resolve to the names of your rollable tables. For example, if the fighter had a STR of 16 and used it on a Kobold (STR 7), then you would want to roll on the "Compare9" table to give you the text "Superior". Example for STR/DEX/CON /w gm &{template:npcaction} {{rname=Know Your Enemy}} {{name=@{selected|token_name} vs. @{target|Enemy|token_name}}} {{description=Attribute Comparison **STR:** [[1t[Compare[[@{selected|strength}-@{target|Enemy|strength}]]]]]] **DEX:** [[1t[Compare[[@{selected|dexterity}-@{target|Enemy|dexterity}]]]]]] **CON:** [[1t[Compare[[@{selected|constitution}-@{target|Enemy|constitution}]]]]]]}} Output Caveats For some reason, templates (I tried npcaction, traits, and default) seem to only allow this technique to work for up to three lines (3 tables). Any more than that will only output the inline roll text. So, you'd have to have a second macro for the remaining attributes, or live with some non-ideal formatting. Secondly, if you wanted it for hp, you would have to have a lot of rollable tables. As it is, you'd already have to have a bunch of tables to make this work for the other attributes. If the table doesn't exist, the macro will hang on the target selection window. I'd recommend manual comparison for hp as Jarren suggested. Actually tbh, I'd personally just use Jarren's manual comparison macro lol, but wanted to chime in with the hacky way just in case :) Finally, if you want it all in one macro and don't mind having the AC comparison formatted differently, you could use this: /w gm &{template:npcaction} {{rname=Know Your Enemy}} {{name=@{selected|token_name} vs. @{target|Enemy|token_name}}} {{description=Stat PC vs NPC **STR:** [[1t[Compare[[@{selected|strength}-@{target|Enemy|strength}]]]]]] **DEX:** [[1t[Compare[[@{selected|dexterity}-@{target|Enemy|dexterity}]]]]]] **CON:** [[1t[Compare[[@{selected|constitution}-@{target|Enemy|constitution}]]]]]] **HP:**     **@{selected|hp} | @{target|Enemy|bar1}** }} /w gm **AC:** [[1t[Compare[[@{selected|ac}-@{target|Enemy|npc_ac}]]]]]]
Getting into this thread like a dirty shirt to ask if someone has devised an API-enabled way to handle the battlemaster's "Know Your Enemy" ability? I've got a BM fighter in my campaign and having this in my toolbox would definitely encourage her to use the power more frequently.
1611077637

Edited 1611088160
David M.
Pro
API Scripter
Jay, you can try this. Requires hp to be in bar1 of npc token (the hp attribute doesn't seem to always be populated for dragged out creatures). Also requires selected PC token to represent a "PC" style character sheet, and the target token to represent a "NPC" style character sheet. Put together quickly, so haven't tested much, but you get the idea. syntax !know @{selected|token_id} @{target|Enemy|token_id} script const knowYourEnemy = (() => { const version = '0.1.0'; const scriptName = 'KnowYourEnemy'; const checkInstall = () => { log('-=> ' + scriptName + ' v' + version); }; function processInlinerolls(msg) { if(_.has(msg,'inlinerolls')){ return _.chain(msg.inlinerolls) .reduce(function(m,v,k){ var ti=_.reduce(v.results.rolls,function(m2,v2){ if(_.has(v2,'table')){ m2.push(_.reduce(v2.results,function(m3,v3){ m3.push(v3.tableItem.name); return m3; },[]).join(', ')); } return m2; },[]).join(', '); m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } else { return msg.content; } } const compare = (pcVal, npcVal) => { pcVal = Number(pcVal); npcVal = Number(npcVal); if (pcVal > npcVal) { return 'Superior'; } else if (pcVal < npcVal) { return 'Inferior'; } else { return 'Equal'; } } const handleInput = (msg) => { let pcTok, npcTok; let pcChar, npcChar; if (msg.type=="api" && msg.content.indexOf("!know") === 0 ) { try { who = getObj('player',msg.playerid).get('_displayname'); //Check for inline rolls in script input let inlineContent = processInlinerolls(msg); let args = inlineContent.split(/\s+/); args.shift(); //Get Tokens pcTok = getObj("graphic", args[0]); npcTok = getObj("graphic", args[1]); //Get Characters pcChar = getObj("character", pcTok.get("represents")); npcChar = getObj("character", npcTok.get("represents")); //PC values let pcName = pcTok.get("name"); let pcSTR = getAttrByName(pcChar.id,'strength'); let pcDEX = getAttrByName(pcChar.id,'dexterity'); let pcCON = getAttrByName(pcChar.id,'constitution'); let pcAC = getAttrByName(pcChar.id,'ac'); let pcHP = getAttrByName(pcChar.id,'hp'); //NPC values let npcName = npcTok.get("name"); let npcSTR = getAttrByName(npcChar.id,'strength'); let npcDEX = getAttrByName(npcChar.id,'dexterity'); let npcCON = getAttrByName(npcChar.id,'constitution'); let npcAC = getAttrByName(npcChar.id,'npc_ac'); let npcHP = npcTok.get("bar1_value"); //Let's compare values let strResult = compare(pcSTR, npcSTR); let dexResult = compare(pcDEX, npcDEX); let conResult = compare(pcCON, npcCON); let acResult = compare(pcAC, npcAC); let hpResult = compare(pcHP, npcHP); //output let output = `&{template:npcaction} {{rname=Know Your Enemy}} {{name= ${pcName} vs ${npcName}}} {{description=**STR: ${strResult}** **DEX: ${dexResult}** **CON: ${conResult}** **AC: ${acResult}** **HP: ${hpResult}** }}`; sendChat('', `/w GM ` + output); } catch(err) { sendChat('',`/w "${who}" `+ 'Error: ' + err.message); } }; }; const registerEventHandlers = () => { on('chat:message', handleInput); }; on('ready', () => { checkInstall(); registerEventHandlers(); }); })(); output
David M. said: Jay, you can try this. Requires hp to be in bar1 of npc token (the hp attribute doesn't seem to always be populated for dragged out creatures). Also requires selected PC token to represent a "PC" style character sheet, and the target token to represent a "NPC" style character sheet. Put together quickly, so haven't tested much, but you get the idea. syntax !know @{selected|token_id} @{target|Enemy|token_id} script const knowYourEnemy = (() => { const version = '0.1.0'; const scriptName = 'KnowYourEnemy'; const checkInstall = () => { log('-=> ' + scriptName + ' v' + version); }; function processInlinerolls(msg) { if(_.has(msg,'inlinerolls')){ return _.chain(msg.inlinerolls) .reduce(function(m,v,k){ var ti=_.reduce(v.results.rolls,function(m2,v2){ if(_.has(v2,'table')){ m2.push(_.reduce(v2.results,function(m3,v3){ m3.push(v3.tableItem.name); return m3; },[]).join(', ')); } return m2; },[]).join(', '); m['$[['+k+']]']= (ti.length && ti) || v.results.total || 0; return m; },{}) .reduce(function(m,v,k){ return m.replace(k,v); },msg.content) .value(); } else { return msg.content; } } const compare = (pcVal, npcVal) => { pcVal = Number(pcVal); npcVal = Number(npcVal); if (pcVal > npcVal) { return 'Superior'; } else if (pcVal < npcVal) { return 'Inferior'; } else { return 'Equal'; } } const handleInput = (msg) => { let pcTok, npcTok; let pcChar, npcChar; if (msg.type=="api" && msg.content.indexOf("!know") === 0 ) { try { who = getObj('player',msg.playerid).get('_displayname'); //Check for inline rolls in script input let inlineContent = processInlinerolls(msg); let args = inlineContent.split(/\s+/); args.shift(); //Get Tokens pcTok = getObj("graphic", args[0]); npcTok = getObj("graphic", args[1]); //Get Characters pcChar = getObj("character", pcTok.get("represents")); npcChar = getObj("character", npcTok.get("represents")); //PC values let pcName = pcTok.get("name"); let pcSTR = getAttrByName(pcChar.id,'strength'); let pcDEX = getAttrByName(pcChar.id,'dexterity'); let pcCON = getAttrByName(pcChar.id,'constitution'); let pcAC = getAttrByName(pcChar.id,'ac'); let pcHP = getAttrByName(pcChar.id,'hp'); //NPC values let npcName = npcTok.get("name"); let npcSTR = getAttrByName(npcChar.id,'strength'); let npcDEX = getAttrByName(npcChar.id,'dexterity'); let npcCON = getAttrByName(npcChar.id,'constitution'); let npcAC = getAttrByName(npcChar.id,'npc_ac'); let npcHP = npcTok.get("bar1_value"); //Let's compare values let strResult = compare(pcSTR, npcSTR); let dexResult = compare(pcDEX, npcDEX); let conResult = compare(pcCON, npcCON); let acResult = compare(pcAC, npcAC); let hpResult = compare(pcHP, npcHP); //output let output = `&{template:npcaction} {{rname=Know Your Enemy}} {{name= ${pcName} vs ${npcName}}} {{description=**STR: ${strResult}** **DEX: ${dexResult}** **CON: ${conResult}** **AC: ${acResult}** **HP: ${hpResult}** }}`; sendChat('', `/w "${who}" ` + output); } catch(err) { sendChat('',`/w "${who}" `+ 'Error: ' + err.message); } }; }; const registerEventHandlers = () => { on('chat:message', handleInput); }; on('ready', () => { checkInstall(); registerEventHandlers(); }); })(); output Thank you!! Will try this out tonight.
Did a quick test. It looks great and seems to work fine! However, it outputs all the token's values to the chat window for the GM, who then has to select two of the comparisons to reveal to the player (since the BM only finds out two of the five values per use). Would it be possible to select different comparisons (say, Con and AC, or Str and HP, etc.) from that chat window and have a "send to player" button? I realize it may not be possible / you may not have time or interest to investigate, so no worries if you can't do it, but I just thought I'd ask. Thanks again! David M. said: Jay, you can try this. Requires hp to be in bar1 of npc token (the hp attribute doesn't seem to always be populated for dragged out creatures). Also requires selected PC token to represent a "PC" style character sheet, and the target token to represent a "NPC" style character sheet. Put together quickly, so haven't tested much, but you get the idea. syntax !know @{selected|token_id} @{target|Enemy|token_id}
1611088137

Edited 1611088218
David M.
Pro
API Scripter
Ah, didn't realize that's how the ability worked. It actually currently sends the whispered chat message to whomever called it, which is obviously not desirable given the clarification. I changed the above script to send it to the GM instead, who can then just report the two results to the player. Chat menus can be a bit fiddly. I'd have to look into it when I have more time.
David M. said: Ah, didn't realize that's how the ability worked. It actually currently sends the whispered chat message to whomever called it, which is obviously not desirable given the clarification. I changed the above script to send it to the GM instead, who can then just report the two results to the player. Chat menus can be a bit fiddly. I'd have to look into it when I have more time. Thanks for the quick response!
Jarren, Thanks for the suggestion of using a target token, but I'm not sure all the stats are resident on the sheet behind it, so my approach was more question/response in structure. Player kicks off initial macro directed to themself that puts this up in the chat window: That player clicks two of the listed attributes, which send a message to the GM that looks like The GM clicks the pink attribute, triggering yet a third macro to fill in the request which gets sent back to the player: Overall more convoluted than I would like, but I didn't see an easier way to make it smooth in realtime during a game. This puts the requests to the GM, allows them to respond with a couple clicks, and doesn't rely on sheets being current or too much prep for it to work properly. It does rely on the player/character name being in the macro so it's not spamming the chat, but not that bad. Thank you all for the time, and I love the API approach. Just not at that level currently. M