import { streamDeck, macroControl, otherControls, tokenHelper, getPermission } from "../../MaterialDeck.js"; import { } from "../misc.js"; export class TokenControl{ constructor(){ this.active = false; this.wildcardOffset = 0; this.itemOffset = 0; } async update(tokenId=null){ if (this.active == false) return; for (let device of streamDeck.buttonContext) { if (device?.buttons == undefined) continue; for (let i=0; i e.label === condition).icon; if (tokenHelper.getConditionActive(token,condition)){ ring = 2; ringColor = "#FF7B00"; } } } else if (settings.onClick == 'wildcard') { //wildcard images if (getPermission('TOKEN','WILDCARD') == false ) { streamDeck.noPermission(context,device); return; } if (icon != 'stats') return; const method = settings.wildcardMethod ? settings.wildcardMethod : 'iterate'; let value = parseInt(settings.wildcardValue); if (isNaN(value)) value = 1; const images = await token.actor.getTokenImages(); let currentImgNr = 0 let imgNr; for (let i=0; i= images.length) imgNr -= images.length; while (imgNr < 0) imgNr += images.length; iconSrc = images[imgNr]; } else if (method == 'set'){ imgNr = value - 1 + this.wildcardOffset; if (value >= images.length) iconSrc = "modules/MaterialDeck/img/black.png"; else iconSrc = images[imgNr]; ring = 1; if (currentImgNr == imgNr) { ring = 2; ringColor = "#FF7B00"; } } else return; } else if (settings.onClick == 'initiative') //Initiative iconSrc = "modules/MaterialDeck/img/token/init.png"; } else if (mode == 'offset') { iconSrc = "modules/MaterialDeck/img/black.png"; const itemOffset = settings.itemOffset ? settings.itemOffset : 0; ringColor = settings.offRing ? settings.offRing : '#000000'; const ringOnColor = settings.onRing ? settings.onRing : '#00FF00'; ring = 1; if (itemOffset == this.itemOffset) { ring = 2; ringColor = ringOnColor; } } else if (mode == 'offsetRel') { } else if (mode == 'dispOffset') { iconSrc = "modules/MaterialDeck/img/black.png"; const prependTitle = settings.offsetPrepend ? settings.offsetPrepend : ''; txt += prependTitle + this.itemOffset; } //Items else { txt += prependTitle; const allItems = token.actor.items; let itemNr = settings.itemNr ? settings.itemNr - 1 : 0; itemNr += this.itemOffset; const displayUses = settings.displayUses ? settings.displayUses : false; const displayName = settings.displayInventoryName ? settings.displayInventoryName : false; const displayIcon = settings.displayInventoryIcon ? settings.displayInventoryIcon : false; const selectionMode = settings.inventorySelection ? settings.inventorySelection : 'order'; let items = allItems; let item; if (mode == 'inventory') { items = tokenHelper.getItems(token,settings.inventoryType); items = this.sortItems(items); if (selectionMode == 'order') item = items[itemNr]; else if (selectionMode == 'name') item = items.filter(i => i.name == settings.itemName)[0]; else if (selectionMode == 'id') item = items.filter(i => i.id == settings.itemName)[0]; if (item != undefined && displayUses) uses = tokenHelper.getItemUses(item); } else if (mode == 'features') { items = tokenHelper.getFeatures(token,settings.featureType); items = this.sortItems(items); if (selectionMode == 'order') item = items[itemNr]; else if (selectionMode == 'name') item = items.filter(i => i.name == settings.itemName)[0]; else if (selectionMode == 'id') item = items.filter(i => i.id == settings.itemName)[0]; if (item != undefined && displayUses) uses = tokenHelper.getFeatureUses(item); } else if (mode == 'spellbook') { items = tokenHelper.getSpells(token,settings.spellType,settings.spellMode); items = this.sortItems(items); if (selectionMode == 'order') item = items[itemNr]; else if (selectionMode == 'name') item = items.filter(i => i.name == settings.itemName)[0]; else if (selectionMode == 'id') item = items.filter(i => i.id == settings.itemName)[0]; if (displayUses && item != undefined) uses = tokenHelper.getSpellUses(token,settings.spellType,item); } if (item != undefined) { if (displayIcon) iconSrc = item.img; if (displayName) txt = item.name; } } } //No valid token found: else { if (mode == 'token') { iconSrc += ""; if (settings.onClick == 'visibility') { //toggle visibility if (getPermission('TOKEN','VISIBILITY') == false ) { streamDeck.noPermission(context,device); return; } if (icon == 'stats') { iconSrc = window.CONFIG.controlIcons.visibility; ring = 2; overlay = true; } } else if (settings.onClick == 'combatState') { //toggle combat state if (getPermission('TOKEN','COMBAT') == false ) { streamDeck.noPermission(context,device); return; } if (icon == 'stats') { iconSrc = window.CONFIG.controlIcons.combat; ring = 2; overlay = true; } } else if (settings.onClick == 'target') { //target token if (icon == 'stats') { iconSrc = "fas fa-bullseye"; ring = 2; overlay = true; } } else if (settings.onClick == 'condition') { //toggle condition if (getPermission('TOKEN','CONDITIONS') == false ) { streamDeck.noPermission(context,device); return; } ring = 1; overlay = true; if (icon == 'stats') iconSrc = tokenHelper.getConditionIcon(settings.condition); } else if (settings.onClick == 'cubCondition') { //Combat Utility Belt conditions if (getPermission('TOKEN','CONDITIONS') == false ) { streamDeck.noPermission(context,device); return; } const condition = settings.cubConditionName; if (condition == undefined || condition == '') return; if (icon == 'stats') { iconSrc = CONFIG.statusEffects.find(e => e.label === condition).icon; } ring = 1; overlay = true; } } } if (icon == 'stats' && mode != 'offset' && mode != 'offsetRel'){ if (getPermission('TOKEN','STATS') == false) stats = statsOld; if (stats == 'HP') //HP iconSrc = "modules/MaterialDeck/img/token/hp_empty.png"; if (stats == 'TempHP') //Temp HP iconSrc = "modules/MaterialDeck/img/token/temp_hp_empty.png"; else if (stats == 'AC' || stats == 'ShieldHP' || stats == 'KinAC') //AC iconSrc = "modules/MaterialDeck/img/token/ac.webp"; else if (stats == 'Speed') //Speed iconSrc = "modules/MaterialDeck/img/token/speed.webp"; else if (stats == 'Init' || settings.onClick == 'initiative') //Initiative iconSrc = "modules/MaterialDeck/img/token/init.png"; else if (stats == 'PassivePerception') { iconSrc = "modules/MaterialDeck/img/token/skills/prc.png"; overlay = true; ring = 1; } else if (stats == 'PassiveInvestigation') { iconSrc = "modules/MaterialDeck/img/token/skills/inv.png"; overlay = true; ring = 1; } else if (stats == 'Ability' || stats == 'AbilityMod' || stats == 'Save') { overlay = true; let ability = (stats == 'Save') ? settings.save : settings.ability; if (ability == undefined) ability = 'str'; if (ability == 'con') iconSrc = "modules/MaterialDeck/img/token/abilities/cons.png"; else iconSrc = "modules/MaterialDeck/img/token/abilities/" + ability + ".png"; } else if (stats == 'Skill') { overlay = true; let skill = settings.skill; if (skill == undefined) skill = 'acr'; else iconSrc = "modules/MaterialDeck/img/token/skills/" + (skill.startsWith('lor')? 'lor' : skill) + ".png"; } else if (settings.onClick == 'center' || settings.onClick == 'centerSelect') { overlay = true; iconSrc = "modules/MaterialDeck/img/move/center.png"; } else if (settings.onClick == 'move') { overlay = true; const dir = settings.dir ? settings.dir : 'center'; if (dir == 'up') //up iconSrc = "modules/MaterialDeck/img/move/up.png"; else if (dir == 'down') //down iconSrc = "modules/MaterialDeck/img/move/down.png"; else if (dir == 'right') //right iconSrc = "modules/MaterialDeck/img/move/right.png"; else if (dir == 'left') //left iconSrc = "modules/MaterialDeck/img/move/left.png"; else if (dir == 'upRight') iconSrc = "modules/MaterialDeck/img/move/upright.png"; else if (dir == 'upLeft') iconSrc = "modules/MaterialDeck/img/move/upleft.png"; else if (dir == 'downRight') iconSrc = "modules/MaterialDeck/img/move/downright.png"; else if (dir == 'downLeft') iconSrc = "modules/MaterialDeck/img/move/downleft.png"; } else if (settings.onClick == 'rotate') { overlay = true; const value = isNaN(parseInt(settings.rotValue)) ? 0 : parseInt(settings.rotValue); if (value >= 0) iconSrc = "modules/MaterialDeck/img/move/rotatecw.png"; else iconSrc = "modules/MaterialDeck/img/move/rotateccw.png"; } } if (forceIcon) { iconSrc = forceIcon; txt = ""; } else if (hideName) txt = ""; if (settings.iconOverride != '' && settings.iconOverride != undefined) iconSrc = settings.iconOverride; streamDeck.setIcon(context,device,iconSrc,{background:background,ring:ring,ringColor:ringColor,overlay:overlay,uses:uses,hp:hp}); streamDeck.setTitle(txt,context); } sortItems(items) { let sorted = Object.values(items); sorted.sort((a,b) => a.sort - b.sort); return sorted; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// async keyPress(settings){ const tokenId = canvas.tokens.controlled[0]?.id; const selection = settings.selection ? settings.selection : 'selected'; const tokenIdentifier = settings.tokenName ? settings.tokenName : ''; const mode = settings.tokenMode ? settings.tokenMode : 'token'; let token = tokenHelper.getToken(selection,tokenIdentifier); if (token == undefined) return; if (token.owner == false && token.observer == true && getPermission('TOKEN','OBSERVER') == false ) return; if (token.owner == false && token.observer == false && getPermission('TOKEN','NON_OWNED') == false ) return; if (mode == 'token') { const onClick = settings.onClick ? settings.onClick : 'doNothing'; if (onClick == 'doNothing') //Do nothing return; else if (onClick == 'select'){ //select token token.control(); } else if (onClick == 'center'){ //center on token let location = token.getCenter(token.x,token.y); canvas.animatePan(location); } else if (onClick == 'centerSelect'){ //center on token and select const location = token.getCenter(token.x,token.y); canvas.animatePan(location); token.control(); } else if (onClick == 'move') { //move token tokenHelper.moveToken(token,settings.dir); } else if (onClick == 'rotate') { //rotate token tokenHelper.rotateToken(token,settings.rot,settings.rotValue); } else if (onClick == 'charSheet'){ //Open character sheet const element = document.getElementById(token.actor.sheet.id); if (element == null) token.actor.sheet.render(true); else token.actor.sheet.close(); } else if (onClick == 'tokenConfig') { //Open token config const element = document.getElementById(token.sheet.id); if (element == null) token.sheet.render(true); else token.sheet.close(); } else if (onClick == 'visibility') { //Toggle visibility if (getPermission('TOKEN','VISIBILITY') == false ) return; token.toggleVisibility(); } else if (onClick == 'combatState') { //Toggle combat state if (getPermission('TOKEN','COMBAT') == false ) return; token.toggleCombat(); } else if (onClick == 'target') { //Target token token.setTarget(!token.isTargeted,{releaseOthers:false}); } else if (onClick == 'condition') { //Handle condition if (getPermission('TOKEN','CONDITIONS') == false ) return; const func = settings.conditionFunction ? settings.conditionFunction : 'toggle'; if (func == 'toggle'){ //toggle await tokenHelper.toggleCondition(token,settings.condition); this.update(tokenId); } else if (func == 'increase'){ //increase await tokenHelper.modifyConditionValue(token, settings.condition, +1) this.update(tokenId); } else if (func == 'decrease'){ //decrease await tokenHelper.modifyConditionValue(token, settings.condition, -1) this.update(tokenId); } } else if (onClick == 'cubCondition') { //Combat Utility Belt conditions if (getPermission('TOKEN','CONDITIONS') == false ) return; const condition = settings.cubConditionName; if (condition == undefined || condition == '') return; const effect = CONFIG.statusEffects.find(e => e.label === condition); await token.toggleEffect(effect); this.update(tokenId); } else if (onClick == 'vision'){ if (getPermission('TOKEN','VISION') == false ) return; let sight = {}; let light = { animation: {} } //Vision basic config if (settings.visionEnabled && settings.visionEnabled != 'noChange') { if (settings.visionEnabled == 'toggle') sight.enabled = !token.document.sight.enabled; else sight.enabled = settings.visionEnabled == 'enable'; } if (settings.visionRange && isNaN(settings.visionRange) == false) sight.range = parseInt(settings.visionRange); if (settings.visionDimRange && isNaN(settings.visionDimRange) == false) sight.dimSight = parseInt(settings.visionDimRange); if (settings.visionBrightRange && isNaN(settings.visionBrightRange) == false) sight.brightSight = parseInt(settings.visionBrightRange); if (settings.visionAngle && isNaN(settings.visionAngle) == false) sight.angle = parseInt(settings.visionAngle); if (settings.visionMode && settings.visionMode != 'noChange') sight.visionMode = settings.visionMode; //Vision detection modes let detectionModes = token.document.detectionModes; if (settings.visionDetectionModeEnable && settings.visionDetectionModeEnable != 'noChange' && settings.visionDetectionModeNumber && detectionModes[settings.visionDetectionModeNumber-1] != undefined) { if (settings.visionDetectionModeEnable == 'toggle') detectionModes[settings.visionDetectionModeNumber-1].enabled = !detectionModes[settings.visionDetectionModeNumber-1].enabled; else if (settings.visionDetectionModeEnable == 'enable') detectionModes[settings.visionDetectionModeNumber-1].enabled = true; else if (settings.visionDetectionModeEnable == 'disable') detectionModes[settings.visionDetectionModeNumber-1].enabled = false; detectionModes = detectionModes; } //Vision advanced options if (settings.visionColorEnable) sight.color = settings.visionColor ? (settings.visionColor == '#000000' ? null : settings.visionColor) : null; if (settings.visionAttenuationEnable) sight.attenuation = settings.visionAttenuation ? parseFloat(settings.visionAttenuation) : 0; if (settings.visionBrightnessEnable) sight.brightness = settings.visionBrightness ? parseFloat(settings.visionBrightness) : 0; if (settings.visionSaturationEnable) sight.saturation = settings.visionSaturation ? parseFloat(settings.visionSaturation) : 0; if (settings.visionContrastEnable) sight.contrast = settings.visionContrast ? parseFloat(settings.visionContrast) : 0; //Light basic config if (settings.lightDimRadius && isNaN(settings.lightDimRadius) == false) light.dim = parseInt(settings.lightDimRadius); if (settings.lightBrightRadius && isNaN(settings.lightBrightRadius) == false) light.bright = parseInt(settings.lightBrightRadius); if (settings.lightEmissionAngle && isNaN(settings.lightEmissionAngle) == false) light.angle = parseInt(settings.lightEmissionAngle); if (settings.lightColorEnable) light.color = settings.lightColor ? (settings.lightColor == '#000000' ? null : settings.lightColor) : null; if (settings.lightColorIntensityEnable) light.alpha = settings.lightColorIntensity ? parseFloat(settings.lightColorIntensity) : 0; //Light animation if (settings.lightAnimationType && settings.lightAnimationType != 'noChange') light.animation.type = settings.lightAnimationType == 'none' ? null : settings.lightAnimationType; if (settings.lightAnimationSpeedEnable) light.animation.speed = settings.lightAnimationSpeed ? parseFloat(settings.lightAnimationSpeed) : 5; if (settings.lightAnimationReverseDirection && settings.lightAnimationReverseDirection != 'noChange') { if (settings.lightAnimationReverseDirection == 'toggle') light.animation.reverse = !token.document.light.animation.reverse; else if (settings.lightAnimationReverseDirection == 'enable') light.animation.reverse = true; else if (settings.lightAnimationReverseDirection == 'disable') light.animation.reverse = false; } if (settings.lightAnimationIntensityEnable) light.animation.intensity = settings.lightAnimationIntensity ? parseFloat(settings.lightAnimationIntensity) : 5; //Light advanced options if (settings.lightColorationTechnique && settings.lightColorationTechnique != 'noChange') light.coloration = parseInt(settings.lightColorationTechnique); if (settings.lightLuminosityEnable) light.luminosity = settings.lightLuminosity ? parseFloat(settings.lightLuminosity) : 0.5; if (settings.lightGradualIllumination && settings.lightGradualIllumination != 'noChange') { if (settings.lightGradualIllumination == 'toggle') light.gradual = !token.data.light.gradual; else if (settings.lightGradualIllumination == 'enable') light.gradual = true; else if (settings.lightGradualIllumination == 'disable') light.gradual = false; } if (settings.lightAttenuationEnable) light.attenuation = settings.lightAttenuation ? parseFloat(settings.lightAttenuation) : 0.5; if (settings.lightSaturationEnable) light.saturation = settings.lightSaturation ? parseFloat(settings.lightSaturation) : 0; if (settings.lightContrastEnable) light.contrast = settings.lightContrast ? parseFloat(settings.lightContrast) : 0; if (settings.lightShadowsEnable) light.shadows = settings.lightShadows ? parseFloat(settings.lightShadows) : 0; let data; data = { sight, light } token.document.update(data); } else if (onClick == 'initiative'){ tokenHelper.toggleInitiative(token); } else if (onClick == 'wildcard') { //wildcard images if (getPermission('TOKEN','WILDCARD') == false ) return; const method = settings.wildcardMethod ? settings.wildcardMethod : 'iterate'; let value = parseInt(settings.wildcardValue); if (isNaN(value)) value = 1; const images = await token.actor.getTokenImages(); let imgNr; let iconSrc; if (method == 'iterate'){ let currentImgNr = 0 for (let i=0; i= images.length) imgNr -= images.length; while (imgNr < 0) imgNr += images.length; } else if (method == 'set'){ imgNr = value - 1 + this.wildcardOffset; if (value >= images.length || value < 1) return; } else if (method == 'offset'){ this.wildcardOffset = value; this.update(canvas.tokens.controlled[0]?.id); } else return; iconSrc = images[imgNr]; token.document.update({img: iconSrc}); } else if (onClick == 'macro') { //call a macro const settingsNew = { target: token, macroMode: settings.macroMode, macroNumber: settings.macroId, macroArgs: settings.macroArgs } macroControl.keyPress(settingsNew); } else if (onClick == 'roll') { //roll skill/save/ability const rollMode = settings.rollMode ? settings.rollMode : 'default'; let options; if (rollMode == 'default') options = { fastForward: (otherControls.rollOption != 'dialog'), advantage: (otherControls.rollOption == 'advantage'), disadvantage: (otherControls.rollOption == 'disadvantage') } else if (rollMode == 'normal') options = {fastForward:true} else if (rollMode == 'advantage') options = {fastForward:true,advantage:true} else if (rollMode == 'disadvantage') options = {fastForward:true,disadvantage:true} tokenHelper.roll(token,settings.roll,options,settings.rollAbility,settings.rollSkill,settings.rollSave) if (otherControls.rollOption != 'dialog') otherControls.setRollOption('normal'); } else if (onClick == 'custom') {//custom onClick function if (getPermission('TOKEN','CUSTOM') == false ) return; const formula = settings.customOnClickFormula ? settings.customOnClickFormula : ''; if (formula == '') return; let targetArrayTemp; let formulaArrayTemp; let split1 = formula.split(';'); for (let i=0; i 1) targetArray[i] = data[1]; if (i > 1) { if (furnaceArguments != "") furnaceArguments += " "; furnaceArguments += "\"" + targetArray[i] + "\""; } } } if (macro) { const settingsNew = { target: token, macroMode: 'name', macroNumber: targetArray[1], macroArgs: furnaceArguments } macroControl.keyPress(settingsNew); continue; } let formulaArray = this.splitCustom(formulaArrayTemp); let value = 0; let previousOperation = '+'; if (formulaArray.length == 1 && formulaArray[0][0] == '[') value = formulaArray[0].split('[')[1]; else if (formulaArray.length == 1) value = formulaArray[0]; else { for (let i=0; i= val) {value = val-1;} else if (previousOperation == '>' && value <= val) {value = val+1;} else if (previousOperation == '<=' && value > val) {value = val;} else if (previousOperation == '>=' && value < val) {value = val;} } } for (let i=0; i i.name == settings.itemName)[0]; else if (selectionMode == 'id') item = items.filter(i => i.id == settings.itemName)[0]; if (item != undefined) { tokenHelper.rollItem(item, settings, otherControls.rollOption, otherControls.attackMode); } } } splitCustom(string){ const split = string.split('['); let array1 = []; for (let i=0; i0 && split[i][0] != '@' && split[i] != "" && isNaN(split[i])) split[i] = '['+split[i] const split2 = split[i].split(']'); for (let j=0; j') txt += ifA > ifB ? ifThen : ifElse; else if (ifMode == '>=') txt += ifA >= ifB ? ifThen : ifElse; else if (ifMode == '<') txt += ifA < ifB ? ifThen : ifElse; else if (ifMode == '<=') txt += ifA <= ifB ? ifThen : ifElse; else if (ifMode == '==') txt += ifA == ifB ? ifThen : ifElse; } else if (segment.startsWith('@')) txt += this.getCustomDataPath(segment, token); else txt += segment; } return txt; } decodeIfSegment(segment, token) { let split = segment.split(/\(|\)/g); if (split[1].startsWith('@')) return this.getCustomDataPath(split[1], token); return split[1]; } getCustomDataPath(path, token) { const dataPath = path.split('@')[1].split('.'); let data; if (dataPath[0] == 'actor') data = token.actor; else if (dataPath[0] == 'token') data = token; for (let i=1; i