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

Token_mod Move - restrict from going through Walls

Hi, Does anyone have a method for preventing a Token_mod move from going through walls ? Thanks  
1677921341
The Aaron
Roll20 Production Team
API Scripter
There isn't one currently. Sorry about that. 
That's OK. Thanks Aaron :)
1678364825

Edited 1678365503
Oosh
Sheet Author
API Scripter
Edit - Whoops! Forgot to mention this requires PathMath (available in one-click). Depending on your needs, you could use this - it's just some functions ripped out of another script to check for collisions. If a collision is found, it won't move the token, otherwise, it sends the command on to TokenMod. Usage is simple - select a token and use the top/left commands from tokenmod: !tmblock top|+70 left|+70 If it's useful I can probably fix up the command line so it sends the whole thing through to TokenMod if LOS isn't blocked - so you can use other TM commands in the same line and they'll be passed along. Currently, it only passes on the top and left arguments - you can use +, -, or a flat number as an absolute coordinate. It also just throws a public message out if it blocks the command... I didn't spend long on it. /* globals PathMath, findObjs, on, getObj, sendChat */ const tmBlock = (() => { const scriptName = 'tmBlock'; /** * Check for LOS blocking walls between token and light source * @param {object} token token object * @param {object} newPosition { x, y } * @param {number} range pixel range * @param {object} page map page object * @returns {null|object} returns null if no LOS block, or first path object which blocks the light source */ const checkLineOfSight = (token1, newPosition, page) => { const pos1 = { x: parseInt(token1.get('left')), y: parseInt(token1.get('top')) }, pos2 = newPosition, blockingPaths = findObjs({ type: 'path', pageid: page.id, layer: 'walls' }); const losPath = new PathMath.Path([[pos1.x, pos1.y, 0], [pos2.x, pos2.y, 0]]); let losBlocked = null; for (let i=0; i<blockingPaths.length; i++) { let pathData; try { pathData = JSON.parse(blockingPaths[i].get('path')); } catch(e) { console.error(e) } if (!pathData) continue; const pathTop = blockingPaths[i].get('top') - (blockingPaths[i].get('height')/2), pathLeft = blockingPaths[i].get('left') - (blockingPaths[i].get('width')/2); const pathVertices = pathData.map(vertex => [ vertex[1] + pathLeft, vertex[2] + pathTop, 0 ]); const wallPath = new PathMath.Path(pathVertices); const wallSegments = wallPath.toSegments(), losSegments = losPath.toSegments(); for (let w=0; w<wallSegments.length; w++) { if (losBlocked) break; for (let l=0; l<losSegments.length; l++) { const intersect = PathMath.segmentIntersection(wallSegments[w], losSegments[l]);//wallPath.intersects(losPath); if (intersect) { losBlocked = blockingPaths[i]; break; } } } if (losBlocked) break; } return losBlocked; } const handleInput = (msg) => { if (msg.type === 'api' && /^!tmblock/i.test(msg.content)) { const tokens = getSelectedTokens(msg.selected || []); if (!tokens.length) return; const commands = { top: (msg.content.match(/top\|\s*([\S]+)/i)||[])[1], left: (msg.content.match(/left\|\s*([\S]+)/i)||[])[1], }; const newPosition = parseNewPosition(commands, tokens[0]); if (!newPosition) return sendChat(scriptName, `Token isn't moving.`); const blocked = checkLineOfSight(tokens[0], newPosition, getPageOfToken(tokens[0])); if (blocked) { sendChat(scriptName, `Path is blocked!`); } else { const tokenModCommand = `!token-mod --ids ${tokens[0].id} --set ${commands.top ? `top|${commands.top}` : ''} ${commands.left ? `left|${commands.left}` : ''}`; sendChat(scriptName, tokenModCommand); } } } const getPageOfToken = (token) => token && token.id ? getObj('page', token.get('_pageid')) : null; const parseNewPosition = (commands, token) => { const position = [ 'top', 'left' ].reduce((output, direction) => { if (commands[direction]) { const number = parseInt(commands[direction].replace(/\D/g, '')) || 0; const newPosition = /^\+/.test(commands[direction]) ? token.get(direction) + number : /^-/.test(commands[direction]) ? token.get(direction) - number : number; if (direction === 'top') output.y = newPosition; else output.x = newPosition; } return output; }, { x: token.get('left'), y: token.get('top') }); return position; } /** * @param {object[]} selected array of simple token objects * @returns {object[] | null} array of actual token objects */ const getSelectedTokens = (selected) => { const selectedIds = selected && selected.length ? selected.map(sel => sel._id) : null return selectedIds ? selectedIds.map(id => getObj('graphic', id)) : null; } on('ready', () => { on('chat:message', handleInput); }); })();
1678458214
Pat
Pro
API Scripter
Hm, I'd like to try and restart the teleport mod to make it so tokens don't overlap, and part of that is checking for LOS for any move that's not on the teleport pad... can I use this as part of that? It doesn't require tokenmod currently.. 
1678678049

Edited 1678678839
Oosh
Sheet Author
API Scripter
Go for it - it's all ripped out of checkLightLevel and lazily modified, there's probably a much cleaner way to do it! A heap of the lifting is done by the PathMath library anyway, so I can't really say "no" to pinching whatever you want anyway :) The checkLineOfSight function should probably have been written to just take in { x, y } coordinates as arguments instead of tokens to make it more reusable and decoupled from the rest of the light level logic - I'd probably rewrite it that way now that I've had to rip it out of checkLightLevel and reuse it. There's no real reason to require TokenMod either if you're just check for the collisions - this snippet is only written that way because, with a few changes, it can send the entire command on to token mod with all the token mod changes intact, rather than try to recreate them. If it's only Top and Left you're interested in, setting them directly on the token is obviously easier than recreating a token mod CLI string.