//#################################### // Dynamic Lighting Tool // Also available through one-click // https://app.roll20.net/forum/post/11316788/script-dltool-a-dynamic-lighting-control-panel-and-troubleshooter // Video https://youtu.be/hANQr07uhFM?si=RBdG1kaXJygZiUeV&t=228 //author: KeithCurtis //#################################### var API_Meta = API_Meta || {}; API_Meta.dltool = { offset: Number.MAX_SAFE_INTEGER, lineCount: -1 }; { try { throw new Error(''); } catch (e) { API_Meta.dltool.offset = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - (15)); } } /* globals checkLightLevel */ on('ready', () => { const version = '1.0.9'; log('-=> Dynamic Lighting Tool v' + version + ' is loaded. Base command is !dltool'); if (!_.has(state, 'DLTool')) { state.DLTool = { playercCanVision: false, playercCanLight: false, playercCanChecklist: false, }; } const processInlinerolls = (msg) => { if(msg.hasOwnProperty('inlinerolls')){ return msg.inlinerolls .reduce((m,v,k) => { let ti=v.results.rolls.reduce((m2,v2) => { if(v2.hasOwnProperty('table')){ m2.push(v2.results.reduce((m3,v3) => [...m3,(v3.tableItem||{}).name],[]).join(", ")); } return m2; },[]).join(', '); return [...m,{k:`$[[${k}]]`, v:(ti.length && ti) || v.results.total || 0}]; },[]) .reduce((m,o) => m.replace(o.k,o.v), msg.content); } else { return msg.content; } }; const getPageForPlayer = (playerid) => { let player = getObj('player', playerid); if (playerIsGM(playerid)) { return player.get('lastpage') || Campaign().get('playerpageid'); } let psp = Campaign().get('playerspecificpages'); if (psp[playerid]) { return psp[playerid]; } return Campaign().get('playerpageid'); }; const L = (o) => Object.keys(o).forEach(k => log(`${k} is ${o[k]}`)); const decodeUnicode = (str) => str.replace(/%u[0-9a-fA-F]{2,4}/g, (m) => String.fromCharCode(parseInt(m.slice(2), 16))); //tests for existence of ProdWiz in the sandbox const isProd = state.hasOwnProperty('Roll20Pro'); let cmdName = "!dltool"; let theCommand = ''; let theOption = ''; let lines = ''; let pageInfo = ''; let tokenInfo = ''; let lightInfo = ''; let tokenData = ''; let repeatCommand = ` !dltool`; const onButtonSmall = `
`; const offButtonSmall = `
`; const onButtonLarge = `
`; const offButtonLarge = `
`; let offStyle = `'background-color:#333; color:#ccc; text-decoration: none; text-transform: uppercase; font-weight:bold; font-size: 10px; border:0px solid transparent; border-radius:3px; margin: 2px 2px; padding:1px 6px; display:inline-block'`; let onStyle = `'background-color:#3b0; color:#fff; text-decoration: none; text-transform: uppercase; font-weight:bold; font-size: 10px; border:0px solid transparent; border-radius:3px; margin: 2px 2px; padding:1px 6px; display:inline-block'`; let disableStyle = `'background-color:#888; color:#fff; text-decoration: none; text-transform: uppercase; font-weight:bold; font-size: 10px; border:0px solid transparent; border-radius:3px; margin: 2px 3px; padding:1px 6px; display:inline-block'`; const openSection = `
`; const openHeader = `
`; const openSubhead = `
`; const openPageHead = `
`; const openRolll20Subhead = `
`; const openReport = `
`; const closeReport = `
`; const manualWarning = `S`; const spacer = `
`; const stop = `
`; const caution = `
`; const go = `
`; const HR = `
`; //BUTTON: TOGGLE FOR PAGE PROPERTIES const toggle = (size, value, pageProperty) => { //if (pageProperty === "daylight_mode_enabled"){log ("daylight_mode_enabled = " + value)}; let toggleButton = ''; if (size === "large" && (value === true || value === "true" || value === "basic")) { toggleButton = onButtonLarge } if (size === "large" && value === false) { toggleButton = offButtonLarge } if (size === "small" && (value === true || value === "true" || value === "basic")) { toggleButton = onButtonSmall } if (size === "small" && value === false) { toggleButton = offButtonSmall } if (undefined === pageProperty) { pageProperty = ''; } let finalButton = `${toggleButton}`; if (pageProperty === "explorer_mode") { finalButton = `${((value === "basic") ? onButtonSmall : offButtonSmall)}`; } return finalButton; }; //BUTTON: COLOR PICKETR CALLING BUTTON const colorButton = (pageOrToken, property, value) => { if (null === value) value = "transparent"; if (property === "gridcolor" && !value.includes("#")) value = "#"+value; let finalButton = ` `; return finalButton; }; //BUTTON: TOGGLE FOR TOKEN PROPERTIES const toggleToken = (value, tokenProperty, offCode, onCode) => { let toggleButton = ''; let buttonCode; if (value === true || value === "true" || value === "on") { toggleButton = onButtonSmall; buttonCode = offCode; } else { toggleButton = offButtonSmall; buttonCode = onCode; } if (undefined === tokenProperty) { tokenProperty = ''; } let finalButton = `${toggleButton}`; return finalButton; }; //BUTTON: TOGGLE FOR CONFIG PROPERTIES const toggleConfig = (value, configPropertyName) => { let toggleButton = ''; let buttonCode; if (value=== true){ toggleButton = onButtonSmall; buttonCode = "!dltool --"+configPropertyName; }else { toggleButton = offButtonSmall; buttonCode = "!dltool --"+configPropertyName; } let finalButton = `${toggleButton}`; return finalButton; }; //BUTTON: GENERIC FOR DLTOOL const dlButton = (label, link) => { let finalButton = `${label}`; return finalButton; }; //BUTTON: SMALL INLINE BUTTON -- dropped, but keep if needed in future /* const dlSmallButton = (label, link) => { let finalButton = `${label}`; return finalButton; }; */ //BUTTON: TOKEN LIGHT PRESETS const lightButton = (label, dimLight, brightLight, arc, onCode) => { let totalLight = dimLight + brightLight; let cautionText = ((label.includes("Bullseye")) ? " Due to a bug in the Roll20 Mod Script system, you may need to open and close the token settings manually for the changes to take effect." : ""); let finalButton = ``; let conditionalStyle = onStyle; let conditonalLink = `!token-mod --set emits_bright_light|off emits_low_light|off light_angle|360${repeatCommand} --report`; if (tokenData.get("low_light_distance") === totalLight && tokenData.get("bright_light_distance") === brightLight && tokenData.get("directional_bright_light_total") === arc) { if (tokenData.get("emits_bright_light") === false) { conditionalStyle = disableStyle; conditonalLink = "!token-mod --set emits_bright_light|on emits_low_light|on light_angle|360" + repeatCommand + " --report"; } finalButton = `${label}`; } else { finalButton = `${label}`; } return finalButton; }; const visionButton = (label, has_bright_light_vision, has_night_vision, night_vision_distance, hoverText) => { let finalButton = ``; let conditonalLink = `!token-mod --set emits_bright_light|off emits_low_light|off light_angle|360${repeatCommand} --report`; if (tokenData.get("has_bright_light_vision") === has_bright_light_vision && tokenData.get("has_night_vision") === has_night_vision && tokenData.get("night_vision_distance") === night_vision_distance) { conditonalLink = "!token-mod --set has_bright_light_vision|false has_night_vision|false " + repeatCommand; finalButton = `${label}`; } else { conditonalLink = "!token-mod --set has_bright_light_vision|" + has_bright_light_vision + " has_night_vision|" + has_night_vision + " night_vision_distance|" + night_vision_distance + ((night_vision_distance === 60) ? " night_vision_effect|nocturnal" : " ") + repeatCommand; finalButton = `${label}`; } return finalButton; }; //SET VALUE BUTTON FOR ANY const setValue = (value, property, setCode) => { //Edge cases and Roll20 bug correctors if (value === '') { value = "0"; } if (value === null) { value = "None"; } if (value === undefined) { value = " - "; } let openTitle = ""; let closeTitle = ""; if (property === "night_vision_effect") { switch (value) { case "None": openTitle = ``; closeTitle = ``; break; case "Nocturnal": openTitle = ``; closeTitle = ``; break; default: { let dimmingDistance = parseFloat(tokenData.get('night_vision_effect').replace(/^Dimming_/, '')) || 0; dimmingDistance = Math.round(dimmingDistance * tokenData.get('night_vision_distance')); openTitle = ``; closeTitle = `
` + `Dimming Distance starts at: ${openTitle}${dimmingDistance}${closeTitle}`; value = "Dimming"; } } } if (property === "fog_opacity" || property === "daylightModeOpacity" || property === "dim_light_opacity") { value = value * 100; //for display. We want the user to see percentages, not decimals } let finalButton = `${openTitle}${value}${closeTitle}`; return finalButton; }; //GENERATES A LABEL WITH HOVERTEXT const label = (phrase, helptext) => { let finalLabel = `${phrase}`; return finalLabel; }; //GENERATES PICTOS CHARACTER const pictos = (character, color) => { let pictosChar = `${character}`; return pictosChar; }; //GENERATES Arc of Light const lightArc = (degrees) => { let imgPosition = `style= "position:relative; top:-2px;" `; let finalButton = ""; if (degrees < 361) { finalButton = ``; } if (degrees < 290) { finalButton = ``; } if (degrees < 220) { finalButton = ``; } if (degrees < 150) { finalButton = ``; } if (degrees === 0) { finalButton = ``; } return finalButton; }; const getGMPlayers = (pageid) => findObjs({ type: 'player' }) .filter((p) => playerIsGM(p.id)) .filter((p) => undefined === pageid || p.get('lastpage') === pageid) .map(p => p.id); //BECAUSE ROLL20 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); } }; //UTILITY INFO BLOCK let gmUtilityInfo = openSection + ((tokenData !== '') ? dlButton("Why can't this token see?", "!dltool --report|checklist") : dlButton("Why can't this token see?", "!dltool --report|checklist")) + dlButton("Config", "!dltool --report|config") + dlButton("Other things to check for", "!dltool --checklist") + dlButton("Help Center", "https://help.roll20.net/hc/en-us/articles/360045793374-Dynamic-Lighting-Requirements-Best-Practices") + `  ` + dlButton("DL Report ", "!dltool --report") + ` | ` + dlButton("Vision", "!dltool --report|vision") + dlButton("Light", "!dltool --report|light") + dlButton("Page", "!dltool --report|page") + dlButton(" + ", "!dltool --report|extra") + (isProd? "
Prod Wiz: "+dlButton("Main Menu ", "!prod") + " " +dlButton("Map Menu ", "!prod map") : "")+ `
`; let utilityInfo = gmUtilityInfo; on('chat:message', (msg) => { if ('api' !== msg.type) { return; } msg.content = processInlinerolls(msg); let msgTxt = msg.content; repeatCommand = ` !dltool`; let pageData = getObj('page', getPageForPlayer(msg.playerid)); if (msg.content.match(/report\|extra/m)) { repeatCommand = ` !dltool --report|extra`; } if (msg.content.match(/report\|setscale/m)) { repeatCommand = ` !dltool --report|setscale`; } if (msg.content.match(/report\|light/m)) { repeatCommand = ` !dltool --report|light`; } if (msg.content.match(/report\|vision/m)) { repeatCommand = ` !dltool --report|vision`; } if (msg.content.match(/report\|page/m)) { repeatCommand = ` !dltool --report|page`; } //let repeatCommandChecklist = ` !dltool --report|checklist`; //BUTTON: DAYLIGHT PRESETS const daylightButton = (label, value, code) => { let finalButton = ""; let conditionalStyle = onStyle; if (pageData.get("daylightModeOpacity") * 100 === value) { if (pageData.get("daylight_mode_enabled") === false) { conditionalStyle = disableStyle; } finalButton = `${label}`; } else { finalButton = `${label}`; } return finalButton; }; //BUTTON: GRID PRESETS const gridButton = (label, cellwidth, color, transparency, code) => { let finalButton = ""; let conditionalStyle = onStyle; if (pageData.get("grid_opacity") === transparency) { conditionalStyle = onStyle; finalButton = `${label}`; } else { finalButton = `${label}`; } return finalButton; }; const cellWidthButton = (label, value, code) => { let finalButton = ""; let conditionalStyle = onStyle; if (pageData.get("snapping_increment") === value) { if (pageData.get("snapping_increment") !== value) { conditionalStyle = disableStyle; } finalButton = `${label}`; } else { finalButton = `${label}`; } return finalButton; }; //MESSAGE HANDLING // if ((msg.type == "api" && msgTxt.indexOf(cmdName) !== -1 && playerIsGM(msg.playerid)) || (msgTxt === "!dltool --pcchecklist")) { if (msg.type == "api" && msgTxt.indexOf(cmdName) !== -1) { let APIMessage = msg.content; if (APIMessage === "!dltool" || APIMessage === "!dltools") { APIMessage = "!dltool --report"; } let args = APIMessage.split(/\s--/); // let commands = args[1].split(/\s+/); let commands = args[1].match(/(?:[^\s"]+|"[^"]*")+/g); let theMessage = args[2]; let checkLightButton = ""; tokenData = ''; if (msg.selected) { tokenData = getObj('graphic', msg.selected[0]._id); if(!tokenData) { tokenData =''; } } pageData.set('force_lighting_refresh', true); commands.forEach(c => { theCommand = c.split(/\|/)[0]; theOption = c.split(/\|/)[1]; let checklistTokenInfo; let pagePlusInfo; let scaleInfo; //CASES FOR COMMANDS switch (theCommand) { //CASE - REPORT case 'report': { //FULL REPORT //VISION SECTON if (tokenData !== '') { let lightData = ''; if (typeof checkLightLevel !== "undefined") { lightData = checkLightLevel.isLitBy(tokenData.get("id")); checkLightButton = HR + "" + `` + "Token is in " + (lightData.total * 100).toFixed() + "% total light " + dlButton("Report", "!checklight"); } else { checkLightButton = '
' + dlButton("Check Amount of Light on the Token", "!dltool --message --If you want this functionality, you will need to install checkLightLevel, an external script by Oosh. You can find it in One Click install on your Mod page."); } let controllerNames = ""; let controllers = ''; let char = false; if (tokenData.get("represents")) { char = ((tokenData.get("represents")) ? getObj("character", tokenData.get("represents")) : false); } else { char = false; } if (char) { controllers = char.get("controlledby").split(","); if (char.get("controlledby")) { controllers.forEach(c => { controllerNames = controllerNames + ((c === "all") ? "All players" : getObj("player", c).get("_displayname")) + " • "; }); } } else { controllers = tokenData.get("controlledby").split(","); if (tokenData.get("controlledby")) { controllers.forEach(c => { controllerNames = controllerNames + ((c === "all") ? "All players" : getObj("player", c).get("_displayname")) + " • "; }); } } controllerNames = controllerNames + "•"; controllerNames = controllerNames.split(" • •")[0].replace(/\s•\s/g, ", ").replace(/•/, ""); if (controllerNames === '')(controllerNames = "None (GM only by default)"); let tokenName = (tokenData.get("name") || "unnamed"); //REGULAR REPORT tokenInfo = openSection + openSubhead + 'Token Vision
' + openPageHead + '"' + tokenName + '" ' + ((char) ? ' (' + char.get("name") + ')' : '(generic)') + '
Controllers: ' + controllerNames + '
' + toggleToken(tokenData.get("has_bright_light_vision"), "has_bright_light_vision", "!token-mod --set has_bright_light_vision|off has_night_vision|off", "!token-mod --set has_bright_light_vision|on has_limit_field_of_vision|false") + ' Vision  ' + '
' + toggleToken(tokenData.get("has_night_vision"), "has_night_vision", "!token-mod --set has_bright_light_vision|on has_night_vision|off", "!token-mod --set has_bright_light_vision|on has_night_vision|on night_vision_effect|nocturnal") + ' Night Vision ' + setValue(tokenData.get("night_vision_distance"), "night_vision_distance", "!token-mod --set has_bright_light_vision|on has_night_vision|on night_vision_effect|nocturnal night_vision_distance|?{Input new Night Vision distance in feet}") + "ft " + ((tokenData.get("has_night_vision") && tokenData.get("night_vision_distance") < 1) ? `*` : "") + '
' + ' Tint:' + setValue(tokenData.get("night_vision_tint"), "night_vision_tint", "!token-mod --set night_vision_tint|?{Use sparingly. May interact poorly with colored light. Input in hex, rgb or hsv format.|transparent}") + " " + colorButton("token", "night_vision_tint", tokenData.get('night_vision_tint')) + //`
 
` + ` Mode: ` + setValue(tokenData.get("night_vision_effect"), "night_vision_effect", "!token-mod --set night_vision_effect|?{Choose Mode|None,None|Nocturnal,Nocturnal|Dimming,Dimming } ") + `
` + toggleToken(tokenData.get("has_limit_field_of_vision"), "has_limit_field_of_vision", "!token-mod --set has_limit_field_of_vision|off", "!token-mod --set has_limit_field_of_vision|on") + ' Field of Vision  ' + ` ` + setValue(tokenData.get("limit_field_of_vision_total"), "limit_field_of_vision_total", "!token-mod --set limit_field_of_vision_total|?{Input arc of light in degrees}") + "° Arc " + lightArc(tokenData.get("limit_field_of_vision_total")) + ` ${manualWarning}` + '
' + checkLightButton + HR + `${label("Vision Presets:", "These buttons will handle the most common vision cases for DnD 5e.")}
` + visionButton("normal", true, false, tokenData.get("night_vision_distance"), "Standard vision for humans and other characters without darkvision") + " | " + visionButton("Darkvision 60ft", true, true, 60, "Standard darkvision for most characters with this trait") + visionButton("90", true, true, 90, "Many monsters have 90ft of darkvision. Example: Trolls or Night Hags") + visionButton("120", true, true, 120, "Enhanced darkvision used by races such as Drow or Duergar") + `
`; //CHECKLIST REPORT let lightOnTOken = ((lightData) ? lightData.total : -1); checklistTokenInfo = openSection + openSubhead + 'Token Vision Checklist
' + ((tokenData.get("has_bright_light_vision")) ? label(`${go}${tokenName} has vision, but may require a light source. ` + ((lightData) ? `${tokenName} is in ${(lightData.total * 100).toFixed()}% total light` : ''), `Although this token has sight, it still may require a light source from itself or an outside source, or for page settings to grant daylight.`) : label(`${stop}${tokenName} has its vision turned off. It cannot see.` + `
` + ((playerIsGM(msg.playerid) || state.DLTool.playersCanChecklist) ? toggleToken(tokenData.get("has_bright_light_vision"), "has_bright_light_vision", "!token-mod --set has_bright_light_vision|off has_night_vision|off", "!token-mod --set has_bright_light_vision|on has_limit_field_of_vision|false") + ' Vision' : '') + '
', `Without sight turned on, a token cannot utilize dynamic lighting. This is the recommended setting for most NPCs. Too many tokens with sight on the VTT can lead to confusing areas of apparent brightness for the GM`)) + `
` + ((tokenData.get("night_vision_distance") !== 0) ? ((tokenData.get("has_night_vision")) ? label(`${go}${tokenName} has ${tokenData.get("night_vision_distance")}ft of Night Vision, and does not require a light source`, `You may still need to check if it has a non-zero distance on its night vision. Certain modes may affect how it interacts with existing light. For instance, Nocturnal mode can change dim light to bright within the token's Night Vision range.`) : label(`${caution}${tokenName} has Night Vision turned off. It cannot see without a light source.`, `Night Vision allows a token to see without a light source.`) + `
` + ((playerIsGM(msg.playerid) || state.DLTool.playersCanChecklist) ? toggleToken(tokenData.get("has_night_vision"), "has_night_vision", "!token-mod --set has_bright_light_vision|on has_night_vision|off", "!token-mod --set has_bright_light_vision|on has_night_vision|on night_vision_effect|nocturnal") + ' Night Vision' : '') + '
') : ((tokenData.get("has_night_vision")) ? label(`${stop}${tokenName} has Night Vision, but the distance is set for ${tokenData.get("night_vision_distance")}ft. It can see light sources, but if you wish it to see in the dark, you must specify a distance.`, `Certain modes may affect how it interacts with existing light. For instance, Nocturnal mode can change dim light to bright within the token's Night Vision range.`) : label(`${caution}${tokenName} has Night Vision turned off. It cannot see without a light source.`, `Night Vision allows a token to see without a light source.`)) ) + `
` + ((tokenData.get("represents") && typeof getObj('character', tokenData.get("represents")) !== "undefined") ? ((controllerNames !== "None (GM only by default)") ? `${go}This token represents the character ${char.get("name")}, and is under the control of the following players: ${controllerNames}. They are the only ones who can use this token for dynamic lighting.` : label(`${caution}This token represents the character ${char.get("name")}, but has no specified controller. Only the GM can use it for dynamic lighting vision, by pressing Cmd/Ctrl-L.`, `It does not represent a character sheet. If you are setting up a PC, be sure to assign the sheet to the token before saving this as the default token for the sheet. Saving as default should always be the last step. If this is meant to be an NPC it is fine as-is, but you may want to consider assigning control to the GM to avoid some transparency issues when using Exploration Mode.`)) : ((controllerNames !== "None (GM only by default)") ? `${caution}This generic token is under the control of the following players: ${controllerNames}. They are the only ones who can use this token for dynamic lighting. It does not represent a character sheet. If you are setting up a PC, be sure to make all settings and assign the sheet to the token before saving this as the default token for the sheet. Saving as default should always be the last step.` : `${caution}This token has no specified controller. Only the GM can use it for dynamic lighting vision, by pressing Cmd/Ctrl-L.`) ) + `
` + ((tokenData.get("lightColor") !== "transparent" && tokenData.get("has_night_vision")) ? label(`${caution}${tokenName} is emitting tinted light, but also has Night Vision. Night Vision trumps tinted light, and the color will not appear within its limits.`, `Colored light should be used sparingly. It interacts in unexpected ways with other light sources and with night vision. Colored vision is not recommended.`) + `
` : ``) + ((tokenData.get("limit_field_of_vision_total") < 360 && tokenData.get("has_limit_field_of_vision")) ? label(`${caution}${tokenName} has a limited field of vision. It is directional and may not show entire area around token. If the directional value is set to 0, the token will not be able to see anything. ` + ((playerIsGM(msg.playerid) || state.DLTool.playersCanChecklist) ? `You can turn OFF limited field of view with this switch, but due to a Roll20 bug, you will need to open and close the token settings manually for it to take effect.
` + toggleToken(tokenData.get("has_limit_field_of_vision"), "has_limit_field_of_vision", "!token-mod --set has_limit_field_of_vision|off", "!token-mod --set has_limit_field_of_vision|on") + ' Turn off Limited Field of View  ' : ``), `Turn this off to restore a full field of view to the token.`) + `
` : ``) + ((tokenData.get("layer") === "walls") ? `${caution}This token is on the Dynamic Lighting layer.
Dynamic Lighting will only work for the GM, by using Cmd/Ctrl-L, but players are only able to access tokens on the Token layer.
Light emitted by tokens on the Dynamic Lighting Layer can be seen by tokens on the Token layer. ${dlButton("Move token to Token Layer", "!token-mod --set layer|objects")}
` : '') + ((tokenData.get("layer") === "gmlayer") ? `${stop}This token is on the GM layer.
Dynamic Lighting will not function on the GM layer, nor will any light sources provide light.
Players are only able to access tokens on the Token layer. ${dlButton("Move token to Token Layer", "!token-mod --set layer|objects")}
` : '') + ((tokenData.get("layer") === "map") ? `${caution}This token is on the Map layer.
Dynamic Lighting will only work for the GM, by using Cmd/Ctrl-L.
Light emitted by tokens on the Dynamic Lighting Layer can be seen by tokens on the Token layer.
Players are only able to access tokens on the Token layer. ${dlButton("Move token to Token Layer", "!token-mod --set layer|objects")}
` : '') + ((pageData.get("dynamic_lighting_enabled")) ? `${go}Dynamic Lighting is on. All Dynamic Lighting features should be available.` : `${stop}Dynamic Lighting is OFF for this page. No Dynamic Lighting features will function until this is turned on.` + ((playerIsGM(msg.playerid)) ? `
` + toggle("small", pageData.get("dynamic_lighting_enabled"), "dynamic_lighting_enabled") + ' Dynamic Lighting (May require game refresh)
' : ``)) + `
` + ((playerIsGM(msg.playerid)) ? ((pageData.get("dynamic_lighting_enabled") && pageData.get("daylight_mode_enabled")) ? `${go}Daylight Mode is on. No tokens need a specific light source. Daylight Opacity level has been set for ${Math.round(pageData.get("daylightModeOpacity") * 100)}%. Low levels indicate a darker overall light.` : ((lightOnTOken === 0 && !tokenData.get("has_night_vision")) ? stop : caution) + `Daylight Mode is off. Any token without Night Vision will need to have line of sight to a specific light source. ` + ((lightData) ? `${tokenName} is in ${(lightData.total * 100).toFixed()}% total light${((tokenData.get("has_night_vision")) ? ", but has Night Vision." : ", and has no Night Vision. " + `
` + toggleToken(tokenData.get("has_night_vision"), "has_night_vision", "!token-mod --set has_bright_light_vision|on has_night_vision|off", "!token-mod --set has_bright_light_vision|on has_night_vision|on night_vision_effect|nocturnal") + ` Night Vision
`)} ` : '') + `
` + toggle("small", pageData.get("daylight_mode_enabled"), "daylight_mode_enabled") + ' Day Mode
') + `
` : ((pageData.get("dynamic_lighting_enabled") && pageData.get("daylight_mode_enabled")) ? `${go}Daylight Mode is on. No tokens need a specific light source. Daylight Opacity level has been set for ${Math.round(pageData.get("daylightModeOpacity") * 100)}%. Low levels indicate a darker overall light.` : ((lightOnTOken === 0 && !tokenData.get("has_night_vision")) ? stop : caution) + `Daylight Mode is off. Any token without Night Vision will need to have line of sight to a specific light source. ` + ((lightData) ? `${tokenName} is in ${(lightData.total * 100).toFixed()}% total light${((tokenData.get("has_night_vision")) ? ", but has Night Vision." : ", and has no Night Vision. " )} ` : '')) + `
` ) + // ID of page the sender is on : ID of GMPlayers on this page // ((getPageForPlayer(msg.playerid) === getPageForPlayer(getGMPlayers(getPageForPlayer(msg.playerid)))) ? `` : `${caution} GM is either not logged in, or is not on the same page this token is on. If testing vision or light for token against the GM's report, please make sure that both GM and player are on the same page, and that the token has not been split from the party.
`) + (((playerIsGM(msg.playerid) || state.DLTool.playersCanChecklist)) ? dlButton("Token settings don't stick?", "!dltool --default") : '') + `${HR}Key:
` + `${go} No problem.
` + `${caution} Caution. This may give problems in some circumstances.
` + `${stop} Problem that will likely prevent this token from using Dynamic Lighting.
` + ``; checklistTokenInfo = checklistTokenInfo.replace(/ !dltool/g, " !dltool --report|checklist"); } else { tokenInfo = `
No token is selected.
`; checklistTokenInfo = `
No token is selected.
Please select a token and try again.
`; } //LIGHT SECTON if (tokenData !== '') { lightInfo = openSection + openSubhead + 'Token Light' + spacer.replace(`margin:5px`, `margin:2px`) + toggleToken(tokenData.get("emits_bright_light"), "emits_bright_light", "!token-mod --set emits_bright_light|off emits_low_light|off light_angle|360", "!token-mod --set emits_bright_light|on emits_low_light|on light_angle|360") + ' Light  ' + `
` + '
' + spacer.replace(`margin:5px`, `margin:1px`) + 'Bright: ' + setValue(tokenData.get("bright_light_distance"), "bright_light_distance", "!token-mod --set emits_bright_light|on bright_light_distance|?{Input Bright light in feet}") + "ft " + ' | Dim: ' + setValue(tokenData.get("low_light_distance") - tokenData.get("bright_light_distance"), "low_light_distance", "!token-mod --set emits_low_light|on low_light_distance|?{Input Bright light in feet}") + "ft " + `Intensity: ${setValue(tokenData.get("dim_light_opacity"), "dim_light_opacity", "!token-mod --set dim_light_opacity|?{Set Brightness of Dim light|75}")}%` + `
` + HR + //############ Use this section when Roll20 ads directional dim and bright light to the GUI /* `${label("Directional Light: ", "This section limits the arc of emited light, like a flashlight beam. You can set different values for bright and dim light. Due to a Roll20 bug, you may need to open an close the settings for the token for the change to take effect.")}
` + toggleToken(tokenData.get("has_directional_bright_light"), "has_directional_bright_light", "!token-mod --set has_directional_bright_light|off", "!token-mod --set has_directional_bright_light|on") + ' Bright ' + ` ` + setValue(tokenData.get("directional_bright_light_total"), "directional_bright_light_total", "!token-mod --set directional_bright_light_total|?{Input arc of light in degrees}") + "°" + lightArc(tokenData.get("directional_bright_light_total")) + "   " + toggleToken(tokenData.get("has_directional_dim_light"), "has_directional_dim_light", "!token-mod --set has_directional_dim_light|off", "!token-mod --set has_directional_dim_light|on") + ' Dim ' + ` ` + setValue(tokenData.get("directional_dim_light_total"), "directional_dim_light_total", "!token-mod --set directional_dim_light_total|?{Input arc of light in degrees}") + "°" + lightArc(tokenData.get("directional_dim_light_total")) + */ //############ Use this section when Roll20 ads directional dima and bright light to the GUI //############ Remove this section when Roll20 ads directional dima and bright light to the GUI toggleToken(tokenData.get("has_directional_bright_light"), "has_directional_bright_light", "!token-mod --set has_directional_bright_light|off", "!token-mod --set has_directional_bright_light|on") + ' Directional Light ' + ` ` + setValue(tokenData.get("directional_bright_light_total"), "directional_bright_light_total", "!token-mod --set directional_bright_light_total|?{Input arc of light in degrees}") + "° Arc " + lightArc(tokenData.get("directional_bright_light_total")) + ' ' + manualWarning + '' + //############ Use this section when Roll20 ads directional dima and bright light to the GUI '
' + `
` + ' Color:' + setValue(tokenData.get("lightColor"), "lightColor", "!token-mod --set lightColor|?{Use sparingly. Input in hex, rgb or hsv format.|transparent}") + " " + colorButton("token", "lightColor", tokenData.get('lightColor')) + '  Multiplier:' + setValue(tokenData.get("light_sensitivity_multiplier"), "light_sensitivity_multiplier", "!token-mod --set light_sensitivity_multiplier|?{Sometimes called Low Light Vision. 100% is recommended for most RPG systems|100}") + "% " + `
` + `
` + HR + `${label("Light Presets: ", "Presets for most common cases. These are geared toward 5e definitions, but are simple to redefine in the code. Clicking a preset name will also toggle the state of light being on or off. The preset will restored if you toggle the master token light switch off and on.")}
` + lightButton("Spotlight", 0, 5, 360, "!token-mod --set emits_bright_light|on bright_light_distance|5 low_light_distance|0 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Candle", 5, 2, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|2 low_light_distance|5 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Lamp", 15, 15, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|15 low_light_distance|15 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Torch", 20, 20, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|20 low_light_distance|20 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Hooded Lantern", 30, 30, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|30 low_light_distance|30 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + "
" + lightButton("Bullseye Lantern " + pictos("!"), 60, 60, 90, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|60 low_light_distance|60 has_directional_bright_light|on directional_bright_light_total|90 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + "
" + lightButton("Light", 20, 20, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|20 low_light_distance|20 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Daylight", 60, 60, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|60 low_light_distance|60 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Dancing Light", 10, 0, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|0 low_light_distance|10 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Faerie Fire", 10, 0, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|0 low_light_distance|10 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + lightButton("Flametongue", 40, 40, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|40 low_light_distance|40 has_directional_bright_light|off directional_bright_light_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + // lightButton("Gem of Brightness", 30, 30, 360, "!token-mod --set emits_bright_light|on emits_low_light|on bright_light_distance|30 low_light_distance|30 has_limit_field_of_vision|off limit_field_of_vision_total|360 dim_light_opacity|" + tokenData.get("dim_light_opacity")) + ``; } else { tokenInfo = `
No token is selected.
`; } //PAGE SECTON pageInfo = openSection + openSubhead + 'Dynamic Lighting for Page' + openPageHead + '"' + pageData.get("name") + '"' + toggle("small", pageData.get("dynamic_lighting_enabled"), "dynamic_lighting_enabled") + ' Dynamic Lighting' + '  ' + toggle("small", pageData.get("showdarkness"), "showdarkness") + ' Fog of War' + '
' + ' GM Dark Opacity: ' + setValue(pageData.get("fog_opacity"), "fog_opacity", "!dltool-mod --fog_opacity|?{Input value between 0 and 100?|35}") + "%
" + toggle("small", pageData.get("daylight_mode_enabled"), "daylight_mode_enabled") + ' Day Mode  ' + setValue(pageData.get("daylightModeOpacity"), "daylightModeOpacity", "!dltool-mod --daylightModeOpacity|?{Input value between 0 and 100?|100}") + "%
" + toggle("small", pageData.get("lightrestrictmove"), "lightrestrictmove") + ' Barriers Restrict Movement' + manualWarning + '
' + toggle("small", pageData.get("lightupdatedrop"), "lightupdatedrop") + ' Update on Drop' + ' ' + toggle("small", pageData.get("explorer_mode"), "explorer_mode") + ' Explorer Mode' + '
' + HR + `${label("Daylight Presets:", "When Daylight Mode is on, tokens with Vision do not need specific light sources in able to see. These presets simulate regular daylight, a moonlit night, and a starlit night. Can also be used for buildings or dungeons with dim interiors.")} ` + `` + daylightButton("Day", 100, "!dltool --daylight_mode_enabled|true" + " !dltool --daylightModeOpacity|100") + `` + `` + daylightButton("Moon", 50, "!dltool --daylight_mode_enabled|true" + " !dltool --daylightModeOpacity|50") + `` + `` + daylightButton("Star", 20, "!dltool --daylight_mode_enabled|true" + " !dltool --daylightModeOpacity|20") + `` + ``; //PAGEPLUS SECTON let diagonalType = pageData.get("diagonaltype"); switch (pageData.get("diagonaltype")) { case "foure": diagonalType = "DnD 5e-4e"; break; case "pythagorean": diagonalType = "Euclidean"; break; case "threefive": diagonalType = "PF 1&2-Dnd3.5"; break; case "manhattan": diagonalType = "Manhattan"; break; default: diagonalType = pageData.get("diagonaltype"); } pagePlusInfo = openSection + openSubhead + 'Additional Page Settings' + toggle("small", pageData.get("showgrid"), "showgrid") + ' Show Grid' + '
' + 'Grid Opacity: ' + setValue(pageData.get("grid_opacity"), "grid_opacity", "!dltool-mod --grid_opacity|?{Input value between 0 and 1?|1}") + ' Color:' + setValue(pageData.get("gridcolor"), "gridcolor", `!dltool-mod --gridcolor|?{Input value in Hex format|}`) + ' ' + colorButton("page", "gridcolor", pageData.get('gridcolor')) + '
' + 'Grid Type: ' + setValue(pageData.get("grid_type"), "grid_type", "!dltool-mod --grid_type|?{Choose grid type|Square,square|Hex(V),hex|Hex(H),hexr|Dimetric,dimetric|Isometric,isometric}") + (((pageData.get("grid_type") === "hex" || pageData.get("grid_type") === "hexr")) ? " " + toggle("small", pageData.get("gridlabels"), "gridlabels") + ' Show Labels' : "") + (((pageData.get("grid_type") === "square")) ? "  Diag.: " + setValue(diagonalType, "diagonaltype", "!dltool-mod --diagonaltype|?{Choose diagonal measurement method type|Dnd5e-4e,foure |Pathfinder-DnD3.5,threefive|Euclidean,pythagorean|Manhattan,manhattan}") : "") + '
' + 'Page scale: ' + setValue(pageData.get("scale_number"), "scale_number", "!dltool-mod --scale_number|?{Input numerical measurement for one grid unit|5}") + ' ' + setValue(pageData.get("scale_units"), "scale_units", "!dltool-mod --scale_units|?{Input type of unit, example: ft. for feet|ft.}") + ' ' + dlButton("Get from map", "!dltool --report|setscale") + '
' + HR + 'Background Color: ' + setValue(pageData.get("background_color"), "background_color", `!dltool-mod --background_color|?{Input value in Hex format|}`) + ' ' + colorButton("page", "background_color", pageData.get("background_color")) + '
' + 'Change Name: ' + setValue(pageData.get("name"), "name", "!dltool-mod --name|"?{Input new name for page|" + pageData.get("name") + "}"") + '
' + HR + `${label("Cell Width Divisions:", "How many times is one cell divided. This is the same as setting the Cell Width in the Page Settings, but is perhaps more intuitive. 2 divisions is the same as setting the cell width to 0.5. 8 divisions corresonds to 0.125.")}
` + `` + cellWidthButton("1", "1", "!dltool --snapping_increment|1") + `` + `` + cellWidthButton("2", "0.5", "!dltool --snapping_increment|0.5") + `` + `` + cellWidthButton("3", "0.33333", "!dltool --snapping_increment|0.33333") + `` + `` + cellWidthButton("4", "0.25", "!dltool --snapping_increment|0.25") + `` + `` + cellWidthButton("5", "0.2", "!dltool --snapping_increment|0.2") + `` + `` + cellWidthButton("6", "0.16666", "!dltool --snapping_increment|0.16666") + `` + `` + cellWidthButton("7", "0.142857", "!dltool --snapping_increment|0.142857") + `` + `` + cellWidthButton("8", "0.125", "!dltool --snapping_increment|0.125") + `` + '
Cell Width: ' + setValue(pageData.get("snapping_increment"), "snapping_increment", "!dltool-mod --snapping_increment|?{Input positive number?|1}") + (isProd ? HR + openRolll20Subhead + `Roll 20 CNV`+ dlButton("Toggle Buddy", "!prod map buddy") + dlButton("Split Path", "!pathSplit") +'
' + `${label("Grid Presets:", "Grid Presets.")}
` + gridButton("standard","1", "#COCOCO", "0.5", "!dltool-mod --snapping_increment|1" + " !dltool-mod --gridcolor|C0C0C0" + " !dltool-mod --grid_opacity|0.5") + gridButton("weak","1", "#000000", "0.1", "!dltool-mod --snapping_increment|1" + " !dltool-mod --gridcolor|000000" + " !dltool-mod --grid_opacity|0.1") + gridButton("medium","1", "#000000", "0.1", "!dltool-mod --snapping_increment|1" + " !dltool-mod --gridcolor|000000" + " !dltool-mod --grid_opacity|0.3") + gridButton("Strong","1", "#000000", "0.5", "!dltool-mod --snapping_increment|1" + " !dltool-mod --gridcolor|000000" + " !dltool-mod --grid_opacity|0.5") + `
` + gridButton("Invisible","1", "#000000", "0", "!dltool-mod --snapping_increment|1" + " !dltool-mod --gridcolor|000000" + " !dltool-mod --grid_opacity|0") + gridButton("Template","0.125", "000000", "0.25", "!dltool --snapping_increment|0.125" + " !dltool --gridcolor|FF00FF" + " !dltool --grid_opacity|0.25") : "")+ ``; //Set Map from scale scaleInfo = openSection + openSubhead + 'Set Scale from Printed Measurement' + 'Often a map will have a printed scale that does not correspond to a grid setting. This is typically true of city and overland maps. To set the page scale to correspond to the printed scale, first use the Measurement Tool to measure the printed scale. You may need to hold down the alt/opt key to avoid snapping and get a precise measurement. Remember this number, then press the button below and enter that number into the dialog box.
' + dlButton("set scale", "!dltool-mod --scale_number|[[(round((" + pageData.get("scale_number") + "/?{Input value measured from printed scale})*?{Input value as displayed on printed scale}*100))/100]]") + " " + setValue(pageData.get("scale_units"), "scale_units", "!dltool-mod --scale_units|?{Input type of unit, example: mi for miles|mi}") + ' ' + ``; configInfo = openSection + openSubhead + 'Configuration' + 'Use the buttons below to grant specific permissions to the players in your campaign.
' + toggleConfig(state.DLTool.playersCanSight, "playersCanSight") + "Players can set their token's Vision
" + toggleConfig(state.DLTool.playersCanLight, "playersCanLight") + "Players can set their token's Light
" + toggleConfig(state.DLTool.playersCanChecklist, "playersCanChecklist") + "Players can use active Checklist
" + ``; //Blanks out info for players if not allowed. tokenInfo = (!state.DLTool.playersCanSight && !playerIsGM(msg.playerid) ? '' : tokenInfo); lightInfo = (!state.DLTool.playersCanLight && !playerIsGM(msg.playerid) ? '' : lightInfo); pageInfo = (!playerIsGM(msg.playerid) ? '' : pageInfo); pagePlusInfo = (!playerIsGM(msg.playerid) ? '' : pagePlusInfo); if (playerIsGM(msg.playerid)) { utilityInfo = gmUtilityInfo; } else { utilityInfo = openSection + ((tokenData !== '') ? dlButton("Why can't this token see?", "!dltool --report|checklist") : dlButton("Why can't this token see?", "!dltool --report|checklist")) + '
' + dlButton("DL Report ", "!dltool --report") + ` | ` + (!state.DLTool.playersCanSight && !playerIsGM(msg.playerid) ? '' : dlButton("Vision", "!dltool --report|vision")) + (!state.DLTool.playersCanLight && !playerIsGM(msg.playerid) ? '' : dlButton("Light", "!dltool --report|light")) + `` }; //Determines which report to send if (undefined === theOption) { lines = openHeader + 'Dynamic Lighting Tool' + `` + tokenInfo + lightInfo + pageInfo + utilityInfo + ''; } else { switch (theOption) { case "vision": lines = openHeader + 'Dynamic Lighting Tool' + `` + tokenInfo + utilityInfo + ''; break; case "light": lines = openHeader + 'Dynamic Lighting Tool' + `` + lightInfo + utilityInfo + ''; break; case "page": lines = openHeader + 'Dynamic Lighting Tool' + `` + pageInfo + utilityInfo + ''; break; case "extra": lines = openHeader + 'Dynamic Lighting Tool' + `` + pageInfo + pagePlusInfo + utilityInfo + ''; break; case "setscale": lines = openHeader + 'Dynamic Lighting Tool' + `` + scaleInfo + utilityInfo + ''; break; case "config": lines = openHeader + 'Dynamic Lighting Tool' + `` + ((playerIsGM(msg.playerid) || msg.playerid === "API") ? configInfo + gmUtilityInfo : "You must be a GM in order to configure this script") + ''; break; default: lines = openHeader + 'Dynamic Lighting Tool' + `` + checklistTokenInfo + utilityInfo + ''; } } let toWhom = (msg.playerid !=='API' ? '/w "' + getObj("player",msg.playerid).get("_displayname") + '" ' : '/w gm '); /* if (!playerIsGM(msg.playerid)) { lines = openHeader + 'Dynamic Lighting Tool' + `` + checklistTokenInfo + ''; toWhom = '/w ' + getObj("player", msg.playerid).get("_displayname") + ' '; } */ sendChat('DL Tool', toWhom + openReport + lines + closeReport, null, { noarchive: true }); } break; //GENERIC MESSSAGE case 'message': if (undefined !== theMessage) { theMessage = '' + theMessage + ''; sendChat('DLTool', '/w gm ' + openSection + theMessage + closeReport, null, { noarchive: true }); } break; case 'default': theMessage = `${openHeader}Dynamic Lighting Tool${openSection}${openSubhead}Saving a Default Token${spacer}Users can become frustrated when they set up a token perfectly, but the next time they pull it from the Journal Tab, none of the settings seem to have saved.
Setting a default token is like taking a snapshot of a token exactly as it is, and saving it to the character journal. Any changes you make to a token on the VTT will not affect the default token at all.
Therefore, setting the journal's default token must always be done as the last step.` + `${openSection}${openSubhead}Three Ways to Set a Default Token${spacer}1) From the Token Settings
Open the token's Token Settings panel. Click the "Update Default Token" button.
${HR}2) From the Journal
Open the journal for the character the token represents. Click the "Edit" button in the upper right corner. On the edit screen, there are three buttons.
${pictos('L')} Edit Token Properties: Calls up the Token Settings panel for making other changes you wish to become new defaults.

${pictos('L')} Use Selected Token: Sets the selected token as the new default token for that journal.
${pictos('L')} Apply Token Defaults: Overwrites all tokens in play that represent this character to the new defaults. Caution: if you have edited tokens on the board (say, by numbering them), this can overwrite those changes.${HR}3) ${dlButton("Save Token as Default", "!token-mod --set defaulttoken")}`; //theMessage = '' + theMessage + ''; sendChat('DLTool', '/w gm ' + openReport + theMessage + utilityInfo + closeReport, null, { noarchive: true }); break; //CHECKLIST REPORT case 'checklist': { let VS = `
 
`; //Vertical spacer theMessage = `
Check that token is not blocked off by DL lines from seeing the immediate surroundings.${VS}If testing with the player present, make sure the player is looking at their token's immediate area. Shift-Click and hold on the token to pull the player's view to that area. Check the permission settings manually or with with the report to ensure they have control over the token.${VS}Check that the player has not been Split from the Party (Check the Help Center if this is unfamiliar)${VS}If Fog of War or Explorable Darkness (Dynamic Lighting feature that lets the token retain a memory of map areas it has seen) is being used, check that the area the token is in has been cleared. Both of these can be cleared using the darkness tool on the control palette to the left.${VS}Try toggling a light source preset on and off. Sometimes unset default values can cause a DL glitch.${VS}Can you see a rotating cube in this URL? If not, the browser needs to be WebGL compatible (99%+ are).

${HR}Golden Rule: If things don't seem to be working or responding properly,
1) Open and close the token settings manually, and/or 2) Reload the game.
`; theMessage = `${openSubhead}Other things to check for` + theMessage + ''; if (msg.selected) { tokenData = getObj('graphic', msg.selected[0]._id); } sendChat('DLTool', '/w gm ' + openReport + openHeader + 'Dynamic Lighting Tool' + openSection + theMessage + closeReport + utilityInfo + closeReport, null, { noarchive: true }); } break; case 'playersCanSight': if (true){ if (state.DLTool.playersCanSight ? state.DLTool.playersCanSight=false : state.DLTool.playersCanSight=true); } sendChat('DLTool', '!dltool --report|config'); break; case 'playersCanLight': if (true){ if (state.DLTool.playersCanLight ? state.DLTool.playersCanLight=false : state.DLTool.playersCanLight=true); } sendChat('DLTool', '!dltool --report|config'); break; case 'playersCanChecklist': if (true){ if (state.DLTool.playersCanChecklist ? state.DLTool.playersCanChecklist=false : state.DLTool.playersCanChecklist=true); } sendChat('DLTool', '!dltool --report|config'); break; case 'fog_opacity': if (theOption >= 1 && theOption <= 100) { theOption = theOption / 100; } else { if (theOption === "0") { theOption = 0; } else { theOption = 1.0; } } break; case 'daylightModeOpacity': if (theOption >= 1 && theOption <= 100) { theOption = theOption / 100; } else { if (theOption === "0") { theOption = 0; } else { theOption = 1.0; } } pageData.set('force_lighting_refresh', true); break; case 'showdarkness': if (theOption === "false" || theOption === "true") { stringToBoolean(theOption); } else { sendChat('DLTool', '/w gm ' + openReport + theOption + ' is not a valid value for ' + theCommand + ' It has been set to false.' + closeReport, null, { noarchive: true }); theOption = false; } if (pageData.get("dynamic_lighting_enabled") && theOption === "true") { pageData.set('dynamic_lighting_enabled', false); } break; case 'dynamic_lighting_enabled': if (theOption === "false" || theOption === "true") { stringToBoolean(theOption); } else { sendChat('DLTool', '/w gm ' + openReport + theOption + ' is not a valid value for ' + theCommand + ' It has been set to false.' + closeReport, null, { noarchive: true }); theOption = false; } if (pageData.get("showdarkness") && theOption === "true") { pageData.set('showdarkness', false); } pageData.set('force_lighting_refresh', true); break; case 'daylight_mode_enabled': if (theOption === "false" || theOption === "true") { theOption = ((theOption === 'true') ? true : false); } else { theOption = false; sendChat('DLTool', '/w gm ' + openReport + theOption + ' is not a valid value for ' + theCommand + ' It has been set to false.' + closeReport, null, { noarchive: true }); pageData.set('force_lighting_refresh', true); } break; case 'lightrestrictmove': if (theOption === "false" || theOption === "true") { theOption = ((theOption === 'true') ? true : false); } else { theOption = false; sendChat('DLTool', '/w gm ' + openReport + theOption + ' is not a valid value for ' + theCommand + ' It has been set to false.' + closeReport, null, { noarchive: true }); } pageData.set('force_lighting_refresh', true); break; case 'lightupdatedrop': if (theOption === "false" || theOption === "true") { theOption = ((theOption === 'true') ? true : false); } else { theOption = false; sendChat('DLTool', '/w gm ' + openReport + theOption + ' is not a valid value for ' + theCommand + ' It has been set to false.' + closeReport, null, { noarchive: true }); } break; case 'explorer_mode': if (theOption === "off" || theOption === "basic") { theOption = (theOption === 'basic') ? 'basic' : 'off'; } else { theOption = 'off'; sendChat('DLTool', '/w gm ' + openReport + theOption + ' is not a valid value for ' + theCommand + ' It has been set to false.' + closeReport, null, { noarchive: true }); } break; case 'colorpicker': { let dataSet = theOption.split("%%")[0]; let theProperty = theOption.split("%%")[1]; let opacityButton = ""; let colorList = ["#000000", "#434343", "#666666", "#C0C0C0", "#D9D9D9", "#FFFFFF", "#980000", "#FF0000", "#FF9900", "#FFFF00", "#00FF00", "#00FFFF", "#4A86E8", "#0000FF", "#9900FF", "#FF00FF", "#E6B8AF", "#F4CCCC", "#FCE5CD", "#FFF2CC", "#D9EAD3", "#D0E0E3", "#C9DAF8", "#CFE2F3", "#D9D2E9", "#EAD1DC", "#DD7E6B", "#EA9999", "#F9CB9C", "#FFE599", "#B6D7A8", "#A2C4C9", "#A4C2F4", "#9FC5E8", "#B4A7D6", "#D5A6BD", "#CC4125", "#E06666", "#F6B26B", "#FFD966", "#93C47D", "#76A5AF", "#6D9EEB", "#6FA8DC", "#8E7CC3", "#C27BA0", "#A61C00", "#CC0000", "#E69138", "#F1C232", "#6AA84F", "#45818E", "#3C78D8", "#3D85C6", "#674EA7", "#A64D79", "#5B0F00", "#660000", "#783F04", "#7F6000", "#274E13", "#0C343D", "#1C4587", "#073763", "#20124D", "#20124E"]; let colorTable = openHeader + "Pick a color for this property:" + openPageHead + theProperty + ``; colorList.forEach(c => { colorTable = colorTable + ((dataSet === "page") ? ` `; }); colorTable = colorTable + `
`; let TransparencyButton = `` : ` href ="!token-mod --set ${theProperty}|transparent">`) + ` Transparent `; switch (theProperty) { case 'gridcolor': opacityButton = ' Grid Opacity: ' + setValue(pageData.get("grid_opacity"), "grid_opacity", "!dltool-mod --grid_opacity|?{Input value between 0 and 1?|1}"); break; case 'night_vision_tint': opacityButton = ""; //TransparencyButton = `` : ` href ="!token-mod --set ${theProperty}\|none">`) + ` None ` break; case 'lightColor': opacityButton = ''; // opacityButton = 'Color:' + setValue(tokenData.get("lightColor"), "lightColor", "!token-mod --set lightColor|?{Use sparingly. Input in hex, rgb or hsv format.|transparent}") + " " + //`
 
`; break; default: // Nothing here. :) } opacityButton = opacityButton.replace(" !dltool --report", " " + msg.content); colorTable = colorTable + TransparencyButton; //TransparencyButton = `` : ` href ="!token-mod --set ${theProperty}\|transparent">`) + ` Transparent ` colorTable = colorTable + opacityButton; sendChat('DLTool', '/w gm ' + openReport + colorTable + utilityInfo + closeReport, null, { noarchive: true }); //pageData.set('force_lighting_refresh', true); } break; default: // Nothing here. :) } if (theOption === "false") { pageData.set(theCommand, false); pageData.set('force_lighting_refresh', true); } else { if (theCommand.includes("name")) { theOption = theOption.toString().replace(/"/g, ""); } pageData.set(theCommand, theOption); pageData.set('force_lighting_refresh', true); } }); } }); }); { try { throw new Error(''); } catch (e) { API_Meta.dltool.lineCount = (parseInt(e.stack.split(/\n/)[1].replace(/^.*:(\d+):.*$/, '$1'), 10) - API_Meta.dltool.offset); } }