diff --git a/ImageEditor/1.0.0/ImageEditor.js b/ImageEditor/1.0.0/ImageEditor.js index 923346370..895815f8d 100644 --- a/ImageEditor/1.0.0/ImageEditor.js +++ b/ImageEditor/1.0.0/ImageEditor.js @@ -82,6 +82,7 @@ The Image Editor allows you to select any handout that contains
+The Image Editor allows you to select any handout that contains images and modify the style, layout, and properties of those images without manually editing HTML. Changes are written directly back to the handout's notes. +
+ +Type !imageeditor in chat to open the Image Editor interface. The editor opens as a handout called Image Editor in your journal. Only handouts that contain at least one image will appear in the chooser.
Click the Choose Handout button in the top right of the editor. A dropdown will appear listing all handouts that contain images, in alphabetical order. Selecting one will load its images into the editor.
+The name of the currently selected handout appears as a link in the header. Below it, if the currently selected image is preceded by a header in the handout, that header will appear as a secondary link which jumps directly to that section of the handout.
+ +The left panel shows thumbnails of every image found in the selected handout. Click any thumbnail to load that image into the preview panel and populate the properties panel with its current values. The title of the image, if set, appears below each thumbnail.
+ +The center panel shows a large preview of the currently selected image. At the top of the preview panel is a navigation bar with left and right arrow buttons for moving to the previous or next image without scrolling the thumbnail list. The small thumbnails beside the arrows show the adjacent images and are also clickable.
+ +The right panel shows the editable properties of the currently selected image. Click any value to open a prompt where you can enter a new value. Leaving the prompt blank will remove that property from the image entirely.
+ +Sets the title attribute of the image tag. This text appears as a tooltip when the user hovers over the image.
Sets the src attribute, replacing the image with a different one at the given URL.
Applies a float-based layout preset to the image. Options are:
+Sets the width or height of the image. Values must be in pixels (e.g. 200px) or percent (e.g. 50%).
Sets the margin around the image. Accepts 1 to 4 values in pixels or percent, space-separated, following standard CSS margin shorthand (e.g. 8px 16px).
Rounds the corners of the image. Value must be in pixels or percent (e.g. 8px).
Below the properties are quick-apply preset buttons. Clicking a preset merges a predefined set of style values into the image's existing styles, preserving properties like border-radius that are not part of the preset.
!imageeditor — Opens or refreshes the Image Editor interface.!imageeditor --help — Creates or updates this help handout and whispers you a link to it.|
+ Image Editor
+ |
+ + ${centreContent} + | ++ + Choose Handout + + + ? + + | +
|
+
+
+ Thumbnails
+ ${Renderer.thumbs(images)}
+ |
+ + + | +
+
+
+ Properties
+ ${img ? Renderer.controls(img) : 'No image selected '}
+ |
+
No images found in this handout.
' + ); + return; + } + renderEditor(handout, notes, images); + }); + }); + return; + } + + // ------------------------------------------------------------------ + // All other commands operate on the already-chosen handout. + // They use the cached picker list and never scan all handouts. + // ------------------------------------------------------------------ + const handout = getObj('handout', state.ImageEditor.handoutId); + + if (!handout) { + editor.set('notes', Renderer.render({ get: () => null }, [], Utils.getCachedOptions(), null)); + return; + } + + if (handout.get('name') === Config.editorName) { + Utils.whisper(msg.who, 'Cannot edit images in the Image Editor handout.'); + return; + } + + handout.get('notes', notes => { + + let images = Parser.extractImages(notes); + + if (!images.length) { + editor.set('notes', + Renderer.header(handout.get('name'), Utils.getCachedOptions(), null) + + 'No images found in this handout.
' + ); + return; + } + + // --select + if (args.select !== undefined) { + state.ImageEditor.selectedIndex = Math.max(0, + Math.min(images.length - 1, parseInt(args.select))); + } + + let img = images[state.ImageEditor.selectedIndex]; + let style = StyleEngine.parse(img.style); + + // --set + if (args.set) { + args.set.forEach(s => { + const conf = Config.properties[s.property]; + if (!conf) return; + const value = s.value.trim(); + + if (!value) { + if (conf.type === 'string') { img.title = ''; } + else { delete style[s.property]; } + if (s.property === 'layout') style = StyleEngine.applyLayout(style, 'none'); + return; + } + if (conf.type === 'size') { const v = StyleEngine.validateSize(value); if (v) style[s.property] = v; } + if (conf.type === 'margin') { const v = StyleEngine.validateMargin(value); if (v) style.margin = v; } + if (conf.type === 'enum') { + if (conf.values.includes(value) && s.property === 'layout') { + style = StyleEngine.applyLayout(style, value); + style.layout = value; + } + } + if (conf.type === 'string') { + if (s.property === 'url') img.src = value; + else img.title = value; + } + }); + + const newTag = Parser.rebuildTag(img, StyleEngine.serialize(style)); + notes = Parser.replaceImage(notes, img.tag, newTag); + handout.set('notes', notes); + images = Parser.extractImages(notes); + } + + // Re-fetch img after possible --set update + img = images[state.ImageEditor.selectedIndex]; + style = StyleEngine.parse(img.style); + + // --preset + if (args.preset) { + try { + const presetStyles = JSON.parse(decodeURIComponent(args.preset)); + if (presetStyles === null) { + style = {}; + } else { + Object.assign(style, presetStyles); + } + } catch(e) { + log('ImageEditor: failed to parse preset — ' + e); + } + const newTag = Parser.rebuildTag(img, StyleEngine.serialize(style)); + notes = Parser.replaceImage(notes, img.tag, newTag); + handout.set('notes', notes); + images = Parser.extractImages(notes); + } + + renderEditor(handout, notes, images); + }); + }; + + // ================================================== + checkInstall(); + on('chat:message', handleInput); + + // Prime the cache on script load, then render the empty-state panel so + // the handout has content immediately — even if it was just created. + Utils.rebuildHandoutCache(() => { + log('-=> Image Editor: handout cache primed (' + + (state.ImageEditor.cachedHandoutOptions || []).length + ' handouts with images).'); + // Only write the empty panel if the handout is genuinely blank — + // don't overwrite a valid session that survived a sandbox restart. + const editor = Utils.getEditorHandout(); + editor.get('notes', notes => { + if (!notes || !notes.trim()) renderEmptyPanel(); + }); + }); + +}); + +})(); + diff --git a/ImageEditor/ImageEditor.js b/ImageEditor/ImageEditor.js index 923346370..895815f8d 100644 --- a/ImageEditor/ImageEditor.js +++ b/ImageEditor/ImageEditor.js @@ -82,6 +82,7 @@ The Image Editor allows you to select any handout that containsDocumentation for v.${version}
+ ++Supernotes pulls content from a token’s GM Notes field and from other character fields not normally accessible to macros. +If a token represents a character, you may retrieve: +
+ ++Notes may be whispered to the GM, sent to all players, whispered to the sender, or written directly to a named handout. +A footer button may optionally appear on GM whispers, allowing the note to be forwarded to players. +
+ +
+Images, API command buttons, links, markdown image syntax [x](imageURL), and most special characters pass through correctly in both chat and handouts.
+
Special Control Character for Inline GMnotes
+----- +Five dashes placed in the gmnotes of a token indicate that any following content is trested as gm-only text when sent to chat. +
+ +!gmnote +Whispers note to GM.
+ +!pcnote +Sends note to all players.
+ +!selfnote +Whispers note to the command sender.
+ +!gmnote --id-1234567890abcdef!pcnote --bio
+Sends selected character Bio to all players.
+ +!gmnote --charnote
+Whispers character GM Notes to GM.
+ +!pcnote --image --notitle
+Sends first image without revealing title.
+ ++Add a template using: +
+ +--template|templatename
+
++Example: +
+ +!gmnote --template|crt
+!pcnote --template|notebook --bio
+!pcnote --template|faraway --tokenimage
+
++All templates include inline buttons and support Send to Players and Make Handout. +Handouts use Roll20’s native styling for cross-platform reliability. +
+ +| generic Just the facts, Ma'am. Nothing fancy here. ![]() |
+dark As previous, but in reverse. ![]() |
+crt Retro greenscreen for hacking and cyberpunk. Or for reports on that xenomorph hiding on your ship. ![]() |
+
| notebook You know, for kids. Who like to ride bikes. Maybe they attend a school and solve mysteries. ![]() |
+gothic Classic noire horror for contending with Universal monsters or maybe contending with elder gods. ![]() |
+apoc Messages scrawled on a wall. Crumbling and ancient, like the world that was. ![]() |
+
| scroll High fantasy. Or low fantasy—we don't judge. ![]() |
+scroll2 An alternative to scroll, thats even scrollier. ![]() |
+lcars For opening hailing frequencies and to boldly split infinitives that no one has split before! ![]() |
+
| faraway No animated title crawl, but still has that space wizard feel. ![]() |
+steam Gears and brass have changed my life. ![]() |
+western Return with us now to those thrilling days of yesteryear. ![]() |
+
| dragon Three-fivey style ![]() |
+wizard A fifth edition of templates. ![]() |
+strange Other kids who ride bikes and play D&D. ![]() |
+
| gate3 For folks who like the GOTY based on D&D. ![]() |
+choices A second gate-y style, suitable for for the same crowd. ![]() |
+roll20light for when you want your notes to have the feeling of authority ![]() |
+
| roll20dark As before, but.... dark ![]() |
+news Extra! Extra! Read all about it! The ink bleeds through from the other side of the newsprint. ![]() |
+treasure For listing all that loot. ![]() |
+
| vault A comforting style for sheltered people. ![]() |
+path A style that works well with PF2 Adventure Paths ![]() |
+osrblue Gygax-approved. Maybe. The graph paper even has yellowed edges ![]() |
+
| roman Hail Caesar! ![]() |
+dark55 A style to complement the D&D 5.5e (2024) Sheet dark mode ![]() |
+light55 A style to complement the D&D 5.5e (2024) Sheet light mode ![]() |
+
+On installation, Supernotes defaults to the Default roll template. +The configuration dialog allows you to: +
+ ++Supported sheet templates include: +
+ ++If you experience template issues or configuration problems, you may use the buttons below to restore default behavior or re-open the configuration dialog. +
+ +
+Restore Default Template resets Supernotes to the Default roll template.
+Re-Run Configuration opens the configuration dialog to select a sheet template and toggle footer options.
+
/gm, "").replace(/<\/p>/gm, "
").replace("padding:5px'>

`,
+ handoutbuttonstyle: `style='display:inline-block; color:#ccc; font-size:12px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`,
+ whisperStyle: `'background-color:#2b2130; color:#ddd; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#aaa; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+ },
+
+ western: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color:#7e2d40; background-color: transparent;padding: 0px; border: none'`,
+ whisperStyle: `'background-color:#382d1d; color:#ebcfa9; font-style: italic; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px; margin-top:5px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#fabe69; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+ },
+
+ dragon: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color: #0e3365; font-size:14px; background-color: transparent;padding: 0px; border: none'`,
+ whisperStyle: `'display:block; border-width: 5px 0px 5px 0px; border-style: solid; border-color:#58170D; padding:5px; margin-top:9px;'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#0e3365; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+ },
+
+
+
+ wizard: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color: #000; font-size:12px; background-color: transparent;padding: 0px; border: none'`,
+ whisperStyle: `'background-color:#E0E5C1; color:#000; display:block; border-width: 1px; border-width: 1px 0px 1px 0px; border-style: solid; border-color:#58170D; padding:5px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#58170D; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+ },
+
+path: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color: #eee; font-size:12px; background-color: #5e0000; padding: 0px 4px 0px 4px; border-style:solid; border-width: 2px 4px 2px 4px; border-color: #d9c484; text-transformation: all-caps; font-family: "gin", impact, "Arial Bold Condensed", sans-serif;'`,
+ whisperStyle: `'background-color:#dbd1bc; color:#000; display:block; border-width: 1px; margin-top:15px; padding:5px; font-size: 15px; font-family: "Good OT", arial, sans-serif;'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#58170D; background-color: transparent; font-weight:bold; padding: 0px; border: none'`,
+ footer: ""
+},
+
+apoc: {
+ boxcode: `
`
+ },
+
+ roman: {
+ boxcode: `
`
+ },
+
+ notebook: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color:#056b20; font-size:12px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`,
+ whisperStyle: `'background-color:#2b2130; color:#fbfcf0; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; padding:5px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#fff; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+ },
+
+ treasure: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color:#401e00; font-size:14px; font-weight:normal; background-color: transparent; padding: 0px; border: none;'`,
+ whisperStyle: `'background-color:#401e00; color:#eee; font-family: Tahoma, serif; display:block; border-width: 1px; border-style: solid; border-color:#a3a681; margin-top:10px;padding:5px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#e3b76f; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+ },
+
+choices: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color: #eee; font-size:16px; font-family: "Minion", "Minion Pro", serif; background-color: transparent;padding: 0px; border: none'`,
+ whisperStyle: `'background-image: linear-gradient(to bottom,#4b443d,#3f3732,#4b443d); background-color: transparent; color:#f8e8a6; display:block; border-width: 1px; border: 1px solid #4f4841; margin: 20px, -12px, 15px, -12px; padding:10px, 10px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#eee; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+},
+gate3: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; color: #eee; font-size:16px; font-family: "Minion", "Minion Pro", serif; background-color: transparent;padding: 0px; border: none'`,
+ whisperStyle: `'background-image: linear-gradient(to bottom,#4b443d,#3f3732,#4b443d); background-color: transparent; color:#f8e8a6; display:block; border-width: 1px; border: 1px solid #4f4841; margin: 20px, -12px, 15px, -12px; padding:10px, 10px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#eee; background-color: transparent;padding: 0px; border: none'`,
+ footer: ""
+},
+
+
+ crt: {
+ boxcode: `
`
+ },
+
+ vault: {
+ boxcode: `
`,
+ handoutbuttonstyle: `style='display:inline-block; font-size: 15px !important; color:#fef265; text-shadow: 2px 2px 2px #111;background-color: transparent;padding: 0px; border: none'`,
+ whisperStyle: `'background-color: #transparent; background-image: url(https://files.d20.io/images/459209469/UA2E7Vyf-kncA8k1jUuyAg/original.png; color:#111; display:block; text-shadow: none; text-align:center; font-family: "Contrail One"; border-radius:3px; padding:5px; margin: 15px -20px 10px -20px'`,
+ whisperbuttonstyle: `style='display:inline-block; color:#284a73; background-color: transparent;padding: 0px; border: none'`,
+ footer: ``
+ },
+
+ osrblue: {
+ boxcode: `Documentation for v.${version}
+ ++Supernotes pulls content from a token’s GM Notes field and from other character fields not normally accessible to macros. +If a token represents a character, you may retrieve: +
+ ++Notes may be whispered to the GM, sent to all players, whispered to the sender, or written directly to a named handout. +A footer button may optionally appear on GM whispers, allowing the note to be forwarded to players. +
+ +
+Images, API command buttons, links, markdown image syntax [x](imageURL), and most special characters pass through correctly in both chat and handouts.
+
Special Control Character for Inline GMnotes
+----- +Five dashes placed in the gmnotes of a token indicate that any following content is trested as gm-only text when sent to chat. +
+ +!gmnote +Whispers note to GM.
+ +!pcnote +Sends note to all players.
+ +!selfnote +Whispers note to the command sender.
+ +!gmnote --id-1234567890abcdef!pcnote --bio
+Sends selected character Bio to all players.
+ +!gmnote --charnote
+Whispers character GM Notes to GM.
+ +!pcnote --image --notitle
+Sends first image without revealing title.
+ ++Add a template using: +
+ +--template|templatename
+
++Example: +
+ +!gmnote --template|crt
+!pcnote --template|notebook --bio
+!pcnote --template|faraway --tokenimage
+
++All templates include inline buttons and support Send to Players and Make Handout. +Handouts use Roll20’s native styling for cross-platform reliability. +
+ +| generic Just the facts, Ma'am. Nothing fancy here. ![]() |
+dark As previous, but in reverse. ![]() |
+crt Retro greenscreen for hacking and cyberpunk. Or for reports on that xenomorph hiding on your ship. ![]() |
+
| notebook You know, for kids. Who like to ride bikes. Maybe they attend a school and solve mysteries. ![]() |
+gothic Classic noire horror for contending with Universal monsters or maybe contending with elder gods. ![]() |
+apoc Messages scrawled on a wall. Crumbling and ancient, like the world that was. ![]() |
+
| scroll High fantasy. Or low fantasy—we don't judge. ![]() |
+scroll2 An alternative to scroll, thats even scrollier. ![]() |
+lcars For opening hailing frequencies and to boldly split infinitives that no one has split before! ![]() |
+
| faraway No animated title crawl, but still has that space wizard feel. ![]() |
+steam Gears and brass have changed my life. ![]() |
+western Return with us now to those thrilling days of yesteryear. ![]() |
+
| dragon Three-fivey style ![]() |
+wizard A fifth edition of templates. ![]() |
+strange Other kids who ride bikes and play D&D. ![]() |
+
| gate3 For folks who like the GOTY based on D&D. ![]() |
+choices A second gate-y style, suitable for for the same crowd. ![]() |
+roll20light for when you want your notes to have the feeling of authority ![]() |
+
| roll20dark As before, but.... dark ![]() |
+news Extra! Extra! Read all about it! The ink bleeds through from the other side of the newsprint. ![]() |
+treasure For listing all that loot. ![]() |
+
| vault A comforting style for sheltered people. ![]() |
+path A style that works well with PF2 Adventure Paths ![]() |
+osrblue Gygax-approved. Maybe. The graph paper even has yellowed edges ![]() |
+
| roman Hail Caesar! ![]() |
+dark55 A style to complement the D&D 5.5e (2024) Sheet dark mode ![]() |
+light55 A style to complement the D&D 5.5e (2024) Sheet light mode ![]() |
+
+On installation, Supernotes defaults to the Default roll template. +The configuration dialog allows you to: +
+ ++Supported sheet templates include: +
+ ++If you experience template issues or configuration problems, you may use the buttons below to restore default behavior or re-open the configuration dialog. +
+ +
+Restore Default Template resets Supernotes to the Default roll template.
+Re-Run Configuration opens the configuration dialog to select a sheet template and toggle footer options.
+
$1') + .replace(/\*\*(.*)\*\*/gim, '$1') + .replace(/\*(.*)\*/gim, '$1') + .replace(/!\[(.*?)\]\((.*?)\)/gim, "
/gm, "").replace(/<\/p>/gm, "
").replace("padding:5px'>
${option} option`
+ }
+
+ if (handoutTitle === '') {
+ //Crops out GM info on player messages
+ if (isGM) {
+ //message = (message.includes("-----") ? message.split('-----')[0] + "/i,'
') +.replace(/(
|<\/p>)/,'')
+.replace(/>
/i,'>');
+
+
+
+
+
+
+// message = ((undefined !== message) ? message.replace(/\[([^\]]*?)\]\(([^\)]*?)\)(?$1
/gm, "").replace(/<\/p>/gm, "
").replace("padding:5px'>
${option} option`;
+ sendMessage(whom, messagePrefix, template, title, theText, message, tokenID, playerButton, handoutButton);
+
+ }
+ }));
+ } else {
+ (theToken || [])
+ .map(o => getObj('graphic', o._id))
+ .filter(g => undefined !== g)
+ .filter((o) => {
+ const gm = (o && o.get) ? o.get('gmnotes') : '';
+ return !!(gm && gm.length > 0);
+})
+ .forEach(o => {
+ if (regex) {
+ message = _.filter(unescape(decodeUnicode(o.get('gmnotes'))).split(/(?:[\n\r]+|


+The Token Home script allows tokens to store and recall multiple +named locations on the current page. Each location records an X/Y position and +the token’s layer. Tokens can be sent back to saved locations, queried, or +summoned to a selected anchor point based on proximity. +
+ ++This script is a blow-up and glow-up of a script written for me by the Aaron, +years ago. Anything about it that is broken is mine. :) +
+ +Base Command: !home
--set — Store the selected token’s current position as a location.--L# — Recall the selected token to a stored location. That's an L (upper or lower case), followed by an integer.--summon — Pull tokens to a selected anchor based on proximity.--clear — Remove stored location data from selected tokens.--help — Open this help handout.--macro — Creates a generic chat menu macro that you can modify — Coming in the next merge.
+Locations are identified by numbered slots:
+L1, L2, L3, and higher.
+There is no fixed upper limit. This is how I use them, but you can use whatever
+location logic works for you;
+
Each stored location records:
+ +Format:
++!home --set --L# ++ +
+Stores the selected token’s current position and layer into location L N.
+
!home --set --L1 — Set default location!home --set --L2 — Set residence!home --set --L5 — Set custom locationFormat:
++!home --L# ++ +
+Moves the selected token to the stored location L N.
+
!home --L1!home --L3+The summon command pulls tokens toward a selected anchor object +based on proximity to their stored locations. +
+ +Format:
++!home --summon [--L#] [--r pixels or grid squares] ++ +
+If no value is given, pixels are assumed. Use g for grid squares.
+
Examples of valid radius values:
+ +--r300 = 300 pixels--r5g = 5 grid squaresExactly one object must be selected. That object can be a:
+ +graphic)text)pin)+The selected object’s X/Y position is used as the summon target. +
+ +--L# — Restrict the summon to a specific stored location--r|pixels — Maximum distance from the anchor (default: 70)--L# is supplied, only that location is tested!home --summon!home --summon --r|210!home --summon --L2!home --summon --L4 --r|140Format:
++!home --clear [--L#] ++ +
--L# is supplied, only that location is removed+I use a macro for most of these commands, which has buttons for up to 4 locations. +The macro labels these as default, residence, work, and encounter, but you can +name these however you wish. +
+ +You can create this sample macro with the command !home --macro:
+/w gm &{template:default} {{name=Token Home}}{{Default=[Set](!home --set --l1) [Go](!home --l1) [Near](!home --summon --l1) [Radius](!home --summon --l1 --radius|?{Input number of pixels})}}{{Residence=[Set](!home --set --l2) [Go](!home --l2) [Near](!home --summon --l2) [Radius](!home --summon --l2 --radius|?{Input number of pixels})}}{{Work=[Set](!home --set --l3) [Go](!home --l3) [Near](!home --summon --l3) [Radius](!home --summon --l3 --radius|?{Input number of pixels})}}{{Encounter=[Set](!home --set --l4) [Go](!home --l4) [Near](!home --summon --l4) [Radius](!home --summon --l4 --radius|?{Input number of pixels})}}{{Summon Any=[Near](!home --summon) [Within X Pixels](!home --summon --radius|?{Input number of pixels})}}
+
+
+
+`;
+
+ /*************************
+ * HELP HANDOUT
+ *************************/
+const showHomeHelp = () => {
+ let handout = findObjs({
+ type: 'handout',
+ name: HOME_HELP_NAME
+ })[0];
+
+ if (!handout) {
+ handout = createObj('handout', {
+ name: HOME_HELP_NAME,
+ avatar: HOME_HELP_AVATAR,
+ notes: HOME_HELP_TEXT,
+ inplayerjournals: 'gm',
+ controlledby: 'gm'
+ });
+ } else {
+ // Ensure content stays current
+ handout.set({
+ avatar: HOME_HELP_AVATAR,
+ notes: HOME_HELP_TEXT
+ });
+ }
+
+sendChat(
+ 'TokenHome', `/w gm The Token Home script allows tokens to store and recall multiple -named locations on the current page. -Each location records an X/Y position and the token’s layer. +named locations on the current page. Each location records an X/Y position and +the token’s layer. Tokens can be sent back to saved locations, queried, or +summoned to a selected anchor point based on proximity.
-Tokens can be sent back to saved locations, queried, or summoned to a selected -anchor point based on proximity. +This script is a blow-up and glow-up of a script written for me by the Aaron, +years ago. Anything about it that is broken is mine. :)
--set — Store the selected token’s current position as a location.--lN — Recall the selected token to a stored location.--L# — Recall the selected token to a stored location. That's an L (upper or lower case), followed by an integer.--summon — Pull tokens to a selected anchor based on proximity.--clear — Remove stored location data from selected tokens.--help — Open this help handout.--macro — Creates a generic chat menu macro that you can modify — Coming in the next merge.
Locations are identified by numbered slots:
L1, L2, L3, and higher.
-There is no fixed upper limit.
+There is no fixed upper limit. This is how I use them, but you can use whatever
+location logic works for you;
-Each stored location records: -
+Each stored location records:
Format:
-!home --set --lN +!home --set --L#
@@ -114,7 +120,7 @@ Stores the selected token’s current position and layer into location Format:
@@ -143,7 +149,7 @@ Moves the selected token to the stored location Format:
-if no value is given, then pixels are assumed. Use 'g' for grid squares. L N
Rules
-
@@ -122,9 +128,9 @@ Stores the selected token’s current position and layer into location L N
Examples
-
!home --set --l1 — Set default location!home --set --l2 — Set residence!home --set --l5 — Set custom location!home --set --L1 — Set default location!home --set --L2 — Set residence!home --set --L5 — Set custom location
@@ -133,7 +139,7 @@ Stores the selected token’s current position and layer into location L N
-!home --lN
+!home --L#
L N.
Rules
-
@@ -151,8 +157,8 @@ Moves the selected token to the stored location L N.
Examples
-
!home --l1!home --l3!home --L1!home --L3
@@ -166,17 +172,23 @@ based on proximity to their stored locations.
-!home --summon [--lN] [--r pixels or grid squares]
+!home --summon [--L#] [--r pixels or grid squares]
+
--r300
= 300 pixels, --r5g
= 5 grid squares.
+If no value is given, pixels are assumed. Use g for grid squares.
Examples of valid radius values:
+ +--r300 = 300 pixels--r5g = 5 grid squares-Exactly one object must be selected: -
+Exactly one object must be selected. That object can be a:
graphic)--lN--r pixels70.
- --L# — Restrict the summon to a specific stored location--r|pixels — Maximum distance from the anchor (default: 70)--lN is supplied, only that location is tested--L# is supplied, only that location is tested!home --summon!home --summon --r 210!home --summon --l2!home --summon --l4 --r 140!home --summon --r|210!home --summon --L2!home --summon --L4 --r|140Format:
-!home --clear [--lN] +!home --clear [--L#]
--lN is supplied, only that location is removed--L# is supplied, only that location is removed+I use a macro for most of these commands, which has buttons for up to 4 locations. +The macro labels these as default, residence, work, and encounter, but you can +name these however you wish. +
+ +You can create this sample macro with the command !home --macro:
+/w gm &{template:default} {{name=Token Home}}{{Default=[Set](!home --set --l1) [Go](!home --l1) [Near](!home --summon --l1) [Radius](!home --summon --l1 --radius|?{Input number of pixels})}}{{Residence=[Set](!home --set --l2) [Go](!home --l2) [Near](!home --summon --l2) [Radius](!home --summon --l2 --radius|?{Input number of pixels})}}{{Work=[Set](!home --set --l3) [Go](!home --l3) [Near](!home --summon --l3) [Radius](!home --summon --l3 --radius|?{Input number of pixels})}}{{Encounter=[Set](!home --set --l4) [Go](!home --l4) [Near](!home --summon --l4) [Radius](!home --summon --l4 --radius|?{Input number of pixels})}}{{Summon Any=[Near](!home --summon) [Within X Pixels](!home --summon --radius|?{Input number of pixels})}}
+
+
-