diff --git a/MaterialDeck.js b/MaterialDeck.js
index e08548f..527126e 100644
--- a/MaterialDeck.js
+++ b/MaterialDeck.js
@@ -25,6 +25,9 @@ export var selectedTokenId;
let ready = false;
let activeSounds = [];
+
+export let hotbarUses = false;
+export let calculateHotbarUses;
//CONFIG.debug.hooks = true;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -53,19 +56,27 @@ async function analyzeWSmessage(msg){
//console.log("Received",data);
if (data.type == "connected" && data.data == "SD"){
+ const msg = {
+ target: "SD",
+ type: "init",
+ system: game.system.id
+ }
+ ws.send(JSON.stringify(msg));
+
+
+ console.log("streamdeck connected to server");
+ streamDeck.resetImageBuffer();
+ }
+
+ if (data.type == "version" && data.source == "SD") {
/*
console.log(data);
const minimumSDversion = game.modules.get("MaterialDeck").data.minimumSDversion.replace('v','');
const minimumMSversion = game.modules.get("MaterialDeck").data.minimumMSversion;
- console.log('SD',minimumSDversion,minimumMSversion)
- if (data.SDversion < minimumSDversion) console.log('SD: nope')
+ console.log('SD',minimumSDversion,data.version)
+ if (data.version < minimumSDversion) console.log('SD: nope')
else console.log('SD: yes');
- if (data.MSversion < minimumMSversion) console.log('MS: nope')
- else console.log('MS: yes');
*/
-
- console.log("streamdeck connected to server");
- streamDeck.resetImageBuffer();
}
if (data == undefined || data.payload == undefined) return;
@@ -169,7 +180,8 @@ function startWebsocket() {
ws.send(JSON.stringify(msg));
const msg2 = {
target: "SD",
- type: "init"
+ type: "init",
+ system: game.system.id
}
ws.send(JSON.stringify(msg2));
clearInterval(wsInterval);
@@ -195,6 +207,22 @@ export function sendWS(txt){
ws.send(txt);
}
+export function isEmpty(obj) {
+ for(var key in obj) {
+ if(obj.hasOwnProperty(key))
+ return false;
+ }
+ return true;
+}
+
+export function getPermission(action,func) {
+ const role = game.user.role-1;
+ const settings = game.settings.get(moduleName,'userPermission');
+ if (action == 'ENABLE') return settings.enable[role];
+ else return settings.permissions?.[action]?.[func]?.[role];
+}
+
+
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Hooks
@@ -205,25 +233,10 @@ export function sendWS(txt){
* Ready hook
* Attempt to open the websocket
*/
-Hooks.once('ready', ()=>{
+Hooks.once('ready', async()=>{
enableModule = (game.settings.get(moduleName,'Enable')) ? true : false;
- game.socket.on(`module.MaterialDeck`, (payload) =>{
- //console.log(payload);
- if (payload.msgType != "playSound") return;
- playTrack(payload.trackNr,payload.src,payload.play,payload.repeat,payload.volume);
- });
-
- for (let i=0; i<64; i++)
- activeSounds[i] = false;
-
- if (enableModule == false) return;
- if (game.user.isGM == false) {
- ready = true;
- return;
- }
- startWebsocket();
soundboard = new SoundboardControl();
streamDeck = new StreamDeck();
tokenControl = new TokenControl();
@@ -235,6 +248,63 @@ Hooks.once('ready', ()=>{
externalModules = new ExternalModules();
sceneControl = new SceneControl();
+ game.socket.on(`module.MaterialDeck`, async(payload) =>{
+ //console.log(payload);
+ if (payload.msgType == "playSound") playTrack(payload.trackNr,payload.src,payload.play,payload.repeat,payload.volume);
+ else if (game.user.isGM && payload.msgType == "playPlaylist") {
+ const playlist = playlistControl.getPlaylist(payload.playlistNr);
+ playlistControl.playPlaylist(playlist,payload.playlistNr);
+ }
+ else if (game.user.isGM && payload.msgType == "playTrack") {
+ const playlist = playlistControl.getPlaylist(payload.playlistNr);
+ const sounds = playlist.data.sounds;
+ for (let track of sounds)
+ if (track._id == payload.trackId)
+ playlistControl.playTrack(track,playlist,payload.playlistNr)
+ }
+ else if (game.user.isGM && payload.msgType == "stopAllPlaylists")
+ playlistControl.stopAll(payload.force);
+ else if (game.user.isGM && payload.msgType == "soundboardUpdate") {
+ await game.settings.set(moduleName,'soundboardSettings',payload.settings);
+ const payloadNew = {
+ "msgType": "soundboardRefresh"
+ };
+ game.socket.emit(`module.MaterialDeck`, payloadNew);
+ }
+ else if (game.user.isGM == false && payload.msgType == "soundboardRefresh" && enableModule)
+ soundboard.updateAll();
+ else if (game.user.isGM && payload.msgType == "macroboardUpdate") {
+ await game.settings.set(moduleName,'macroSettings',payload.settings);
+ const payloadNew = {
+ "msgType": "macroboardRefresh"
+ };
+ game.socket.emit(`module.MaterialDeck`, payloadNew);
+ }
+ else if (game.user.isGM == false && payload.msgType == "macroboardRefresh" && enableModule)
+ macroControl.updateAll();
+ else if (game.user.isGM && payload.msgType == "playlistUpdate") {
+ await game.settings.set(moduleName,'playlists',payload.settings);
+ const payloadNew = {
+ "msgType": "playlistRefresh"
+ };
+ game.socket.emit(`module.MaterialDeck`, payloadNew);
+ }
+ else if (game.user.isGM == false && payload.msgType == "playlistRefresh" && enableModule)
+ playlistControl.updateAll();
+
+ });
+
+ for (let i=0; i<64; i++)
+ activeSounds[i] = false;
+
+ if (enableModule == false) return;
+ if (getPermission('ENABLE') == false) {
+ ready = true;
+ return;
+ }
+
+ startWebsocket();
+
let soundBoardSettings = game.settings.get(moduleName,'soundboardSettings');
let macroSettings = game.settings.get(moduleName, 'macroSettings');
let array = [];
@@ -261,6 +331,11 @@ Hooks.once('ready', ()=>{
volume: arrayVolume
});
}
+
+ const hotbarUsesTemp = game.modules.get("illandril-hotbar-uses");
+ if (hotbarUsesTemp != undefined) {
+ hotbarUses = true;
+ }
});
@@ -290,6 +365,7 @@ Hooks.on('updateToken',(scene,token)=>{
let tokenId = token._id;
if (tokenId == selectedTokenId)
tokenControl.update(selectedTokenId);
+ if (macroControl != undefined) macroControl.updateAll();
});
Hooks.on('updateActor',(scene,actor)=>{
@@ -302,6 +378,7 @@ Hooks.on('updateActor',(scene,actor)=>{
tokenControl.update(selectedTokenId);
}
}
+ if (macroControl != undefined) macroControl.updateAll();
});
Hooks.on('controlToken',(token,controlled)=>{
@@ -313,8 +390,13 @@ Hooks.on('controlToken',(token,controlled)=>{
selectedTokenId = undefined;
}
tokenControl.update(selectedTokenId);
+ if (macroControl != undefined) macroControl.updateAll();
});
+Hooks.on('updateOwnedItem',()=>{
+ if (macroControl != undefined) macroControl.updateAll();
+})
+
Hooks.on('renderHotbar', (hotbar)=>{
if (enableModule == false || ready == false) return;
if (macroControl != undefined) macroControl.hotbar(hotbar.macros);
@@ -358,6 +440,7 @@ Hooks.on('updateScene',()=>{
Hooks.on('renderSceneControls',()=>{
if (enableModule == false || ready == false || otherControls == undefined) return;
otherControls.updateAll();
+ externalModules.updateAll();
});
Hooks.on('targetToken',(user,token,targeted)=>{
@@ -395,6 +478,16 @@ Hooks.on('gmScreenOpenClose',(html,isOpen)=>{
externalModules.updateAll({gmScreen:isOpen});
});
+Hooks.on('ShareVision', ()=>{
+ if (enableModule == false || ready == false) return;
+ externalModules.updateAll();
+})
+
+Hooks.on('NotYourTurn', ()=>{
+ if (enableModule == false || ready == false) return;
+ externalModules.updateAll();
+})
+
Hooks.once('init', ()=>{
//CONFIG.debug.hooks = true;
registerSettings(); //in ./src/settings.js
diff --git a/changelog.md b/changelog.md
index c667f92..0ecfb77 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,35 @@
# Changelog Material Deck Module
+### v1.3.0 - 25-02-2021
+Additions:
+
+- Material Deck can now be used by players. A 'User Permission Configuration' screen has been added to the module settings where the GM can deside what Material Deck functions are available to users
+- Macro Action: Added support for Illandril's Hotbar Uses (only requires the module to be installed, does not have to be active)
+- Token Action => OnClick: Added support for CUB conditions
+- External Modules => Added support for the 'Trigger Happy' module
+- External Modules => Added support for the 'MookAI' module
+- External Modules => Added support for the 'Shared Vision' module
+- External Modules => Added support for the 'Lock View' module
+- External Modules => Added support for the 'Not Your Turn' module
+
+Fixes:
+
+- Token Action => OnClick: Fixed conditions for pf1e and dnd3.5e
+
+Other Changes:
+
+- Token and Combat Tracker Actions now autodetect the game system
+- Game-system related settings in the SD app unified and improved
+- Image Cache setting is no longer considered experimental
+
+
+Note 1: Because the module can now be used by players, some settings have been moved from 'world' settings to 'client' settings. This means that previous settings have been deleted, and they have to be set up again in the module settings.
+Note 2: You can give users access to the playlists, macro board and soundboard. Currently, everyone has to share the same configuration, so be careful with giving players permission to configure one of them.
+Note 3: Because of the new game system autodetection, some settings for non dnd5e systems might be deleted. You'll have to reconfigure them.
+
+Compatible server app and SD plugin:
+Material Server v1.0.2 (unchanged): https://github.com/CDeenen/MaterialServer/releases
+SD plugin v1.3.0 (must be updated!): https://github.com/CDeenen/MaterialDeck_SD/releases
+
### v1.2.3 - 03-02-2021
Fixes:
diff --git a/lang/en.json b/lang/en.json
index 88963de..799e810 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -2,6 +2,9 @@
"MaterialDeck.Notifications.Disconnected": "Disconnected from Material Server, attempting to reconnect",
"MaterialDeck.Notifications.ConnectFail": "Can't connect to Material Server, retrying",
"MaterialDeck.Notifications.Connected": "Connected",
+ "MaterialDeck.Notifications.Soundboard.NoPermission": "You do not have permission to configure the soundboard",
+ "MaterialDeck.Notifications.Macroboard.NoPermission": "You do not have permission to configure the macro board",
+ "MaterialDeck.Notifications.Playlist.NoPermission": "You do not have permission to configure the playlists",
"MaterialDeck.Sett.Enable": "Enable module",
"MaterialDeck.Sett.Model": "Stream Deck Model",
@@ -10,12 +13,13 @@
"MaterialDeck.Sett.Model_Normal": "Normal or Mobile",
"MaterialDeck.Sett.Model_XL": "XL",
"MaterialDeck.Sett.Help": "Help",
+ "MaterialDeck.Sett.Permission": "User Permission Configuration",
"MaterialDeck.Sett.PlaylistConfig": "Playlist Configuration",
"MaterialDeck.Sett.MacroConfig": "Macro Configuration",
"MaterialDeck.Sett.SoundboardConfig": "Soundboard Configuration",
"MaterialDeck.Sett.ServerAddr": "Material Server Address",
"MaterialDeck.Sett.ServerAddrHint": "The IP address and port of Material Server. The default value will work for 99% of people, only change this if you know what you're doing. Must follow the format [ip_address]:[port], for example: 'localhost:3001' or '192.168.1.1:4000'.",
- "MaterialDeck.Sett.ImageBuffer": "Image Cache Size (EXPERIMENTAL)",
+ "MaterialDeck.Sett.ImageBuffer": "Image Cache Size",
"MaterialDeck.Sett.ImageBufferHint": "Sets the amount of images to store in the image cache. The image cache will locally store all images sent to the Stream Deck. This improves the update speed, but increases memory usage.",
"MaterialDeck.PL.Unrestricted": "Unrestricted",
@@ -46,6 +50,120 @@
"MaterialDeck.Save": "Save",
"MaterialDeck.FxMaster.Colorize": "Colorize",
- "MaterialDeck.FxMaster.Clear": "Clear All"
+ "MaterialDeck.FxMaster.Clear": "Clear All",
+
+
+ "MaterialDeck.Perm.Instructions": "Configure the permission for each Material Deck action.",
+ "MaterialDeck.Perm.DefaultNotification": "Material Deck user permissions have been configured to the default values.",
+
+ "MaterialDeck.Perm.ENABLE.label": "Enable Module",
+ "MaterialDeck.Perm.ENABLE.ENABLE.label": "Enable",
+ "MaterialDeck.Perm.ENABLE.ENABLE.hint": "Allow users to use Material Deck",
+
+ "MaterialDeck.Perm.COMBAT.label": "Combat Tracker",
+ "MaterialDeck.Perm.COMBAT.END_TURN.label": "End Turn",
+ "MaterialDeck.Perm.COMBAT.END_TURN.hint": "Allow users to end their turn",
+ "MaterialDeck.Perm.COMBAT.TURN_DISPLAY.label": "Turn Display",
+ "MaterialDeck.Perm.COMBAT.TURN_DISPLAY.hint": "Allow users to display the turn display",
+ "MaterialDeck.Perm.COMBAT.OTHER_FUNCTIONS.label": "Other Functions",
+ "MaterialDeck.Perm.COMBAT.OTHER_FUNCTIONS.hint": "Allow users to use other functions in the 'Function Mode', such as starting/stopping combat, increasing/decreasing the turn, etc",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_COMBATANTS.label": "Display Combatants",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_COMBATANTS.hint": "Allow users to display the combatants",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_NON_OWNED_STATS.label": "Display Non-Owned and Non-Observer Stats",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_NON_OWNED_STATS.hint": "Allow users to display stats for tokens they do not own or have observer permission for",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_LIMITED_HP.label": "Display Limited HP",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_LIMITED_HP.hint": "Allow users to display the HP of tokens they have Limited permission for",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_OBSERVER_HP.label": "Display Observer HP",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_OBSERVER_HP.hint": "Allow users to display the HP of tokens they have Observer permission for",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_ALL_NAMES.label": "Display All Names",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_ALL_NAMES.hint": "Allow users to display the name of all tokens",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_LIMITED_NAME.label": "Display Limited Name",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_LIMITED_NAME.hint": "Allow users to display the name of tokens they have Limited permission for",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_OBSERVER_NAME.label": "Display Observer Name",
+ "MaterialDeck.Perm.COMBAT.DISPLAY_OBSERVER_NAME.hint": "Allow users to display the name of tokens they have Observer permission for",
+
+ "MaterialDeck.Perm.EXTERNAL.label": "External Modules",
+ "MaterialDeck.Perm.EXTERNAL.FXMASTER.label": "Fx Master",
+ "MaterialDeck.Perm.EXTERNAL.FXMASTER.hint": "Allow users to control the Fx Master module",
+ "MaterialDeck.Perm.EXTERNAL.GM_SCREEN.label": "GM Screen",
+ "MaterialDeck.Perm.EXTERNAL.GM_SCREEN.hint": "Allow users to display a GM screen using the GM Screen module",
+
+ "MaterialDeck.Perm.MACRO.label": "Macros",
+ "MaterialDeck.Perm.MACRO.HOTBAR.label": "Hotbar Macros",
+ "MaterialDeck.Perm.MACRO.HOTBAR.hint": "Allow users to use hotbar macros",
+ "MaterialDeck.Perm.MACRO.MACROBOARD.label": "Macro Board",
+ "MaterialDeck.Perm.MACRO.MACROBOARD.hint": "Allow users to use the macro board",
+ "MaterialDeck.Perm.MACRO.MACROBOARD_CONFIGURE.label": "Configure the Macro Board",
+ "MaterialDeck.Perm.MACRO.MACROBOARD_CONFIGURE.hint": "Allow users to configure the macro board",
+
+ "MaterialDeck.Perm.MOVE.label": "Move",
+ "MaterialDeck.Perm.MOVE.TOKEN.label": "Token",
+ "MaterialDeck.Perm.MOVE.TOKEN.hint": "Allow users to move a controlled token",
+ "MaterialDeck.Perm.MOVE.CANVAS.label": "Canvas",
+ "MaterialDeck.Perm.MOVE.CANVAS.hint": "Allow users to move their canvas",
+
+ "MaterialDeck.Perm.OTHER.label": "Other",
+ "MaterialDeck.Perm.OTHER.PAUSE.label": "Pause/Resume",
+ "MaterialDeck.Perm.OTHER.PAUSE.hint": "Allow users to pause or resume the game",
+ "MaterialDeck.Perm.OTHER.CONTROL.label": "Control Buttons",
+ "MaterialDeck.Perm.OTHER.CONTROL.hint": "Allow users to control the control buttons",
+ "MaterialDeck.Perm.OTHER.DARKNESS.label": "Scene Darkness",
+ "MaterialDeck.Perm.OTHER.DARKNESS.hint": "Allow users to set the scene darkness",
+ "MaterialDeck.Perm.OTHER.DICE.label": "Dice Rolling",
+ "MaterialDeck.Perm.OTHER.DICE.hint": "Allow users to roll dice",
+ "MaterialDeck.Perm.OTHER.TABLES_ALL.label": "Roll Tables (all)",
+ "MaterialDeck.Perm.OTHER.TABLES_ALL.hint": "Allow users to view and roll from all roll tables",
+ "MaterialDeck.Perm.OTHER.TABLES.label": "Roll Tables (observer/owner)",
+ "MaterialDeck.Perm.OTHER.TABLES.hint": "Allow users to view and roll from roll tables that they have observer or owner permission for",
+ "MaterialDeck.Perm.OTHER.SIDEBAR.label": "Sidebar",
+ "MaterialDeck.Perm.OTHER.SIDEBAR.hint": "Allow users to control the sidebar",
+ "MaterialDeck.Perm.OTHER.COMPENDIUM_ALL.label": "Compendium Packs (all)",
+ "MaterialDeck.Perm.OTHER.COMPENDIUM_ALL.hint": "Allow users to open all compendium packs",
+ "MaterialDeck.Perm.OTHER.COMPENDIUM.label": "Compendium Packs (observer/owner)",
+ "MaterialDeck.Perm.OTHER.COMPENDIUM.hint": "Allow users to open compendium packs that they have observer or owner permission for",
+ "MaterialDeck.Perm.OTHER.JOURNAL_ALL.label": "Journals (all)",
+ "MaterialDeck.Perm.OTHER.JOURNAL_ALL.hint": "Allow users to open all journals",
+ "MaterialDeck.Perm.OTHER.JOURNAL.label": "Journals (observer/owner)",
+ "MaterialDeck.Perm.OTHER.JOURNAL.hint": "Allow users to open journals they have observer or owner permission for",
+ "MaterialDeck.Perm.OTHER.CHAT.label": "Chat Messages",
+ "MaterialDeck.Perm.OTHER.CHAT.hint": "Allow users to send chat messages",
+
+ "MaterialDeck.Perm.PLAYLIST.label": "Playlists",
+ "MaterialDeck.Perm.PLAYLIST.PLAY.label": "Control",
+ "MaterialDeck.Perm.PLAYLIST.PLAY.hint": "Allow users to play and pause playlists and tracks",
+ "MaterialDeck.Perm.PLAYLIST.CONFIGURE.label": "Configure",
+ "MaterialDeck.Perm.PLAYLIST.CONFIGURE.hint": "Allow users to configure the playlists",
+
+ "MaterialDeck.Perm.SCENE.label": "Scenes",
+ "MaterialDeck.Perm.SCENE.VISIBLE.label": "Visible Scenes",
+ "MaterialDeck.Perm.SCENE.VISIBLE.hint": "Allow users to view and control the visible scenes",
+ "MaterialDeck.Perm.SCENE.ACTIVE.label": "Active Scene",
+ "MaterialDeck.Perm.SCENE.ACTIVE.hint": "Allow users to view the active scene",
+ "MaterialDeck.Perm.SCENE.DIRECTORY.label": "Scene Directory",
+ "MaterialDeck.Perm.SCENE.DIRECTOR.hint": "Allow users to view and control scenes from the scene directory",
+ "MaterialDeck.Perm.SCENE.NAME.label": "Scene by Name",
+ "MaterialDeck.Perm.SCENE.NAME.hint": "Allow users to view and control any scene by name",
+
+ "MaterialDeck.Perm.SOUNDBOARD.label": "Soundboard",
+ "MaterialDeck.Perm.SOUNDBOARD.PLAY.label": "Enable",
+ "MaterialDeck.Perm.SOUNDBOARD.PLAY.hint": "Allow users to play sounds from the soundboard",
+ "MaterialDeck.Perm.SOUNDBOARD.CONFIGURE.label": "Configure",
+ "MaterialDeck.Perm.SOUNDBOARD.CONFIGURE.hint": "Allow users to configure the soundboard",
+
+ "MaterialDeck.Perm.TOKEN.label": "Token",
+ "MaterialDeck.Perm.TOKEN.STATS.label": "Display Stats",
+ "MaterialDeck.Perm.TOKEN.STATS.hint": "Allow the user to display the stats of the controlled token",
+ "MaterialDeck.Perm.TOKEN.VISIBILITY.label": "Toggle Visibility",
+ "MaterialDeck.Perm.TOKEN.VISIBILITY.hint": "Allow the user to toggle the visibility of the controlled token",
+ "MaterialDeck.Perm.TOKEN.COMBAT.label": "Toggle Combat State",
+ "MaterialDeck.Perm.TOKEN.COMBAT.hint": "Allow the user to toggle the combat state of the controlled token",
+ "MaterialDeck.Perm.TOKEN.VISION.label": "Set Vision",
+ "MaterialDeck.Perm.TOKEN.VISION.hint": "Allow the user to set the vision of the controlled token",
+ "MaterialDeck.Perm.TOKEN.WILDCARD.label": "Wildcard Images",
+ "MaterialDeck.Perm.TOKEN.WILDCARD.hint": "Allow the user to set the controlled token's image using the wildcard image functionality",
+ "MaterialDeck.Perm.TOKEN.CONDITIONS.label": "Set Conditions",
+ "MaterialDeck.Perm.TOKEN.CONDITIONS.hint": "Allow the users to set conditions for the controlled token",
+ "MaterialDeck.Perm.TOKEN.CUSTOM.label": "Custom On-Click",
+ "MaterialDeck.Perm.TOKEN.CUSTOM.hint": "Allow the users to set custom on-click functions"
}
diff --git a/module.json b/module.json
index c580438..df16afd 100644
--- a/module.json
+++ b/module.json
@@ -2,8 +2,8 @@
"name": "MaterialDeck",
"title": "Material Deck",
"description": "Material Deck allows you to control Foundry using an Elgato Stream Deck",
- "version": "1.2.3",
- "minimumSDversion": "1.2.2",
+ "version": "1.3.0",
+ "minimumSDversion": "1.3.0",
"minimumMSversion": "1.0.2",
"author": "CDeenen",
"esmodules": [
diff --git a/src/combattracker.js b/src/combattracker.js
index 8feb843..543d076 100644
--- a/src/combattracker.js
+++ b/src/combattracker.js
@@ -24,8 +24,13 @@ export class CombatTracker{
let src = "modules/MaterialDeck/img/black.png";
let txt = "";
let background = "#000000";
+ settings.combat = true;
if (mode == 'combatants'){
+ if (MODULE.getPermission('COMBAT','DISPLAY_COMBATANTS') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (combat != null && combat != undefined && combat.turns.length != 0){
const initiativeOrder = combat.turns;
let nr = settings.combatantNr - 1;
@@ -49,6 +54,10 @@ export class CombatTracker{
}
}
else if (mode == 'currentCombatant'){
+ if (MODULE.getPermission('COMBAT','DISPLAY_COMBATANTS') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (combat != null && combat != undefined && combat.started){
const tokenId = combat.combatant.tokenId;
tokenControl.pushData(tokenId,settings,context);
@@ -59,6 +68,20 @@ export class CombatTracker{
}
}
else if (mode == 'function'){
+
+ if (ctFunction == 'turnDisplay' && MODULE.getPermission('COMBAT','TURN_DISPLAY') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ else if (ctFunction == 'endTurn' && MODULE.getPermission('COMBAT','END_TURN') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ else if (ctFunction != 'turnDisplay' && ctFunction != 'endTurn' && MODULE.getPermission('COMBAT','OTHER_FUNCTIONS') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
if (ctFunction == 'startStop') {
if (combat == null || combat == undefined || combat.combatants.length == 0) {
src = "modules/MaterialDeck/img/combattracker/startcombat.png";
@@ -75,6 +98,9 @@ export class CombatTracker{
}
}
}
+ else if (ctFunction == 'endTurn') {
+ src = "modules/MaterialDeck/img/combattracker/nextturn.png";
+ }
else if (ctFunction == 'nextTurn') {
src = "modules/MaterialDeck/img/combattracker/nextturn.png";
}
@@ -111,6 +137,20 @@ export class CombatTracker{
if (mode == 'function'){
if (combat == null || combat == undefined) return;
const ctFunction = settings.combatTrackerFunction ? settings.combatTrackerFunction : 'startStop';
+
+ if (ctFunction == 'turnDisplay' && MODULE.getPermission('COMBAT','TURN_DISPLAY') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ else if (ctFunction == 'endTurn' && MODULE.getPermission('COMBAT','END_TURN') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ else if (ctFunction != 'turnDisplay' && ctFunction != 'endTurn' && MODULE.getPermission('COMBAT','OTHER_FUNCTIONS') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
if (ctFunction == 'startStop'){
let src;
let background;
@@ -133,6 +173,7 @@ export class CombatTracker{
else if (ctFunction == 'prevTurn') game.combat.previousTurn();
else if (ctFunction == 'nextRound') game.combat.nextRound();
else if (ctFunction == 'prevRound') game.combat.previousRound();
+ else if (ctFunction == 'endTurn' && game.combat.combatant.owner) game.combat.nextTurn();
}
else {
const onClick = settings.onClick ? settings.onClick : 'doNothing';
diff --git a/src/external.js b/src/external.js
index 0a38370..71f9184 100644
--- a/src/external.js
+++ b/src/external.js
@@ -23,18 +23,26 @@ export class ExternalModules{
this.active = true;
const module = settings.module ? settings.module : 'fxmaster';
- if (module == 'fxmaster') this.updateFxMaster(settings,context);
- else if (module == 'gmscreen') this.updateGMScreen(settings,context);
+ if (module == 'fxmaster') this.updateFxMaster(settings,context);
+ else if (module == 'gmscreen') this.updateGMScreen(settings,context);
+ else if (module == 'triggerHappy') this.updateTriggerHappy(settings,context);
+ else if (module == 'sharedVision') this.updateSharedVision(settings,context);
+ else if (module == 'mookAI') this.updateMookAI(settings,context);
+ else if (module == 'notYourTurn') this.updateNotYourTurn(settings,context);
+ else if (module == 'lockView') this.updateLockView(settings,context);
}
keyPress(settings,context){
if (this.active == false) return;
const module = settings.module ? settings.module : 'fxmaster';
- if (module == 'fxmaster')
- this.keyPressFxMaster(settings,context);
- else if (module == 'gmscreen')
- this.keyPressGMScreen(settings,context);
+ if (module == 'fxmaster') this.keyPressFxMaster(settings,context);
+ else if (module == 'gmscreen') this.keyPressGMScreen(settings,context);
+ else if (module == 'triggerHappy') this.keyPressTriggerHappy(settings,context);
+ else if (module == 'sharedVision') this.keyPressSharedVision(settings,context);
+ else if (module == 'mookAI') this.keyPressMookAI(settings,context);
+ else if (module == 'notYourTurn') this.keyPressNotYourTurn(settings,context);
+ else if (module == 'lockView') this.keyPressLockView(settings,context);
}
@@ -48,6 +56,7 @@ export class ExternalModules{
//FxMaster
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
updateFxMaster(settings,context){
+ if (game.user.isGM == false) return;
const fxmaster = game.modules.get("fxmaster");
if (fxmaster == undefined || fxmaster.active == false) return;
@@ -131,6 +140,7 @@ export class ExternalModules{
}
keyPressFxMaster(settings,context){
+ if (game.user.isGM == false) return;
const fxmaster = game.modules.get("fxmaster");
if (fxmaster == undefined || fxmaster.active == false) return;
@@ -248,6 +258,7 @@ export class ExternalModules{
updateGMScreen(settings,context){
if (this.getModuleEnable("gm-screen") == false) return;
+ if (game.user.isGM == false) return;
const background = settings.gmScreenBackground ? settings.gmScreenBackground : '#000000';
let ring = 1;
@@ -265,6 +276,213 @@ export class ExternalModules{
keyPressGMScreen(settings,context){
if (this.getModuleEnable("gm-screen") == false) return;
+ if (game.user.isGM == false) return;
window['gm-screen'].toggleGmScreenVisibility();
}
-}
\ No newline at end of file
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //Trigger Happy
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ updateTriggerHappy(settings,context) {
+ if (this.getModuleEnable("trigger-happy") == false) return;
+ if (game.user.isGM == false) return;
+
+ const displayName = settings.displayTriggerHappyName ? settings.displayTriggerHappyName : false;
+ const displayIcon = settings.displayTriggerHappyIcon ? settings.displayTriggerHappyIcon : false;
+
+ const background = "#340057";
+ const ringColor = game.settings.get("trigger-happy", "enableTriggers") ? "#A600FF" : "#340057";
+
+ let txt = '';
+ if (displayIcon) streamDeck.setIcon(context,"fas fa-grin-squint-tears",background,2,ringColor);
+ else streamDeck.setIcon(context,'','#000000');
+ if (displayName) txt = 'Trigger Happy';
+
+ streamDeck.setTitle(txt,context);
+ }
+
+ keyPressTriggerHappy(settings,context){
+ if (this.getModuleEnable("trigger-happy") == false) return;
+ if (game.user.isGM == false) return;
+ const mode = settings.triggerHappyMode ? settings.triggerHappyMode : 'toggle';
+
+ let val = game.settings.get("trigger-happy", "enableTriggers");
+ if (mode == 'toggle') val = !val;
+ else if (mode == 'enable') val = true;
+ else if (mode == 'disable') val = false;
+
+ game.settings.set("trigger-happy", "enableTriggers", val);
+
+ const control = ui.controls.controls.find(c => c.name == 'token');
+ if (control == undefined) return;
+ let tool = control.tools.find(t => t.name == 'triggers');
+ if (tool == undefined) return;
+ tool.active = val;
+ ui.controls.render();
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //Shared Vision
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ updateSharedVision(settings,context) {
+ if (this.getModuleEnable("SharedVision") == false) return;
+ if (game.user.isGM == false) return;
+
+ const displayName = settings.sharedVisionName ? settings.sharedVisionName : false;
+ const displayIcon = settings.sharedVisionIcon ? settings.sharedVisionIcon : false;
+
+ const background = "#340057";
+ const ringColor = game.settings.get("SharedVision", "enable") ? "#A600FF" : "#340057";
+
+ let txt = '';
+ if (displayIcon) streamDeck.setIcon(context,"fas fa-eye",background,2,ringColor);
+ else streamDeck.setIcon(context,'','#000000');
+ if (displayName) txt = 'Shared Vision';
+ streamDeck.setTitle(txt,context);
+ }
+
+ keyPressSharedVision(settings,context) {
+ if (this.getModuleEnable("SharedVision") == false) return;
+ if (game.user.isGM == false) return;
+
+ const mode = settings.sharedVisionMode ? settings.sharedVisionMode : 'toggle';
+
+ if (mode == 'toggle') Hooks.call("setShareVision",{enable:'toggle'});
+ else if (mode == 'enable') Hooks.call("setShareVision",{enable:true});
+ else if (mode == 'disable') Hooks.call("setShareVision",{enable:false});
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //Mook AI
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ updateMookAI(settings,context) {
+ if (this.getModuleEnable("mookAI") == false) return;
+ if (game.user.isGM == false) return;
+
+ const displayName = settings.mookName ? settings.mookName : false;
+ const displayIcon = settings.mookIcon ? settings.mookIcon : false;
+
+ const background = "#000000";
+
+ let txt = '';
+ if (displayIcon) streamDeck.setIcon(context,"fas fa-brain",'#000000');
+ else streamDeck.setIcon(context,'','#000000');
+ if (displayName) txt = 'Mook AI';
+ streamDeck.setTitle(txt,context);
+ }
+
+ async keyPressMookAI(settings,context) {
+ if (this.getModuleEnable("mookAI") == false) return;
+ if (game.user.isGM == false) return;
+
+ let mook = await import('../../mookAI/scripts/mookAI.js');
+ let mookAI = new mook.MookAI ();
+ mookAI.takeNextTurn();
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //Not Your Turn!
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ updateNotYourTurn(settings,context) {
+
+ if (this.getModuleEnable("NotYourTurn") == false) return;
+ if (game.user.isGM == false) return;
+
+ const mode = settings.notYourTurnMode ? settings.notYourTurnMode : 'toggle';
+ const displayName = settings.notYourTurnName ? settings.notYourTurnName : false;
+ const displayIcon = settings.notYourTurnIcon ? settings.notYourTurnIcon : false;
+
+ const background = "#340057";
+ let ringColor = "#340057" ;
+
+ let txt = '';
+ let icon = '';
+ if (mode == 'toggle' || mode == 'enable' || mode == 'disable') {
+ icon = "fas fa-fist-raised";
+ txt = "Block Combat Movement";
+ ringColor = game.settings.get('NotYourTurn','enable') ? "#A600FF": "#340057" ;
+ }
+ else {
+ icon = "fas fa-lock";
+ txt = "Block Non-Combat Movement";
+ ringColor = game.settings.get('NotYourTurn','nonCombat') ? "#A600FF": "#340057" ;
+ }
+ if (displayIcon) streamDeck.setIcon(context,icon,background,2,ringColor);
+ else streamDeck.setIcon(context,'','#000000');
+ if (displayName == false) txt = '';
+ streamDeck.setTitle(txt,context);
+ }
+
+ async keyPressNotYourTurn(settings,context) {
+ if (this.getModuleEnable("NotYourTurn") == false) return;
+ if (game.user.isGM == false) return;
+
+ const mode = settings.notYourTurnMode ? settings.notYourTurnMode : 'toggle';
+
+ if (mode == 'toggle') Hooks.call("setNotYourTurn",{combat:'toggle'});
+ else if (mode == 'enable') Hooks.call("setNotYourTurn",{combat:true});
+ else if (mode == 'disable') Hooks.call("setNotYourTurn",{combat:false});
+ else if (mode == 'toggleNonCombat') Hooks.call("setNotYourTurn",{nonCombat:'toggle'});
+ else if (mode == 'enableNonCombat') Hooks.call("setNotYourTurn",{nonCombat:true});
+ else if (mode == 'disableNonCombat') Hooks.call("setNotYourTurn",{nonCombat:false});
+ }
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //Lock View
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ updateLockView(settings,context) {
+
+ if (this.getModuleEnable("LockView") == false) return;
+ if (game.user.isGM == false) return;
+
+ const mode = settings.lockViewMode ? settings.lockViewMode : 'panLock';
+ const displayName = settings.lockViewName ? settings.lockViewName : false;
+ const displayIcon = settings.lockViewIcon ? settings.lockViewIcon : false;
+
+ const background = "#340057";
+ let ringColor = "#340057" ;
+
+ let txt = '';
+ let icon = '';
+ if (mode == 'panLock') {
+ icon = "fas fa-arrows-alt";
+ txt = "Pan Lock";
+ ringColor = canvas.scene.getFlag('LockView', 'lockPan') ? "#A600FF": "#340057" ;
+ }
+ else if (mode == 'zoomLock') {
+ icon = "fas fa-search-plus";
+ txt = "Zoom Lock";
+ ringColor = canvas.scene.getFlag('LockView', 'lockZoom') ? "#A600FF": "#340057" ;
+ }
+ else if (mode == 'boundingBox') {
+ icon = "fas fa-box";
+ txt = "Bounding Box";
+ ringColor = canvas.scene.getFlag('LockView', 'boundingBox') ? "#A600FF": "#340057" ;
+ }
+
+ if (displayIcon) streamDeck.setIcon(context,icon,background,2,ringColor);
+ else streamDeck.setIcon(context,'','#000000');
+ if (displayName == false) txt = '';
+ streamDeck.setTitle(txt,context);
+ }
+
+ async keyPressLockView(settings,context) {
+ if (this.getModuleEnable("LockView") == false) return;
+ if (game.user.isGM == false) return;
+
+ const mode = settings.lockViewMode ? settings.lockViewMode : 'panLock';
+ let toggle = settings.lockViewToggle ? settings.lockViewToggle : 'toggle';
+ if (toggle == 'enable') toggle = true;
+ else if (toggle == 'disable') toggle = false;
+ let msg = {};
+
+ if (mode == 'panLock') msg = {panLock:toggle};
+ else if (mode == 'zoomLock') msg = {zoomLock:toggle};
+ else if (mode == 'boundingBox') msg = {boundingBox:toggle};
+
+ Hooks.call("setLockView",msg);
+ }
+}
+
+
diff --git a/src/macro.js b/src/macro.js
index baee0a4..7ba2692 100644
--- a/src/macro.js
+++ b/src/macro.js
@@ -16,11 +16,12 @@ export class MacroControl{
}
}
- update(settings,context){
+ async update(settings,context){
this.active = true;
const mode = settings.macroMode ? settings.macroMode : 'hotbar';
const displayName = settings.displayName ? settings.displayName : false;
const displayIcon = settings.displayIcon ? settings.displayIcon : false;
+ const displayUses = settings.displayUses ? settings.displayUses : false;
let background = settings.background ? settings.background : '#000000';
let macroNumber = settings.macroNumber;
if (macroNumber == undefined || isNaN(parseInt(macroNumber))) macroNumber = 0;
@@ -30,8 +31,13 @@ export class MacroControl{
let ring = 0;
let name = "";
let src = "";
+ let macroId = undefined;
if (mode == 'macroBoard') { //Macro board
+ if ((MODULE.getPermission('MACRO','MACROBOARD') == false )) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (settings.macroBoardMode == 'offset') { //Offset
const ringOffColor = settings.offRing ? settings.offRing : '#000000';
const ringOnColor = settings.onRing ? settings.onRing : '#00FF00';
@@ -45,23 +51,17 @@ export class MacroControl{
else { //Execute macro
macroNumber += this.offset - 1;
if (macroNumber < 0) macroNumber = 0;
- var macroId = game.settings.get(MODULE.moduleName,'macroSettings').macros[macroNumber];
+ macroId = game.settings.get(MODULE.moduleName,'macroSettings').macros[macroNumber];
background = game.settings.get(MODULE.moduleName,'macroSettings').color[macroNumber];
if (background == undefined) background = '#000000';
- src = "";
-
- if (macroId != undefined){
- let macro = game.macros._source.find(p => p._id == macroId);
- if (macro != undefined) {
- if (displayName) name += macro.name;
- if (displayIcon) src += macro.img;
- }
- }
ring = 0;
}
}
else { //Macro Hotbar
- let macroId
+ if ((MODULE.getPermission('MACRO','HOTBAR') == false )) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (mode == 'hotbar') macroId = game.user.data.hotbar[macroNumber];
else {
let macros;
@@ -74,24 +74,40 @@ export class MacroControl{
if (macros[j].key == macroNumber)
macroId = (macros[j].macro == null) ? undefined : macros[j].macro._id;
}
- }
- let src = "";
- let name = "";
+ }
+ }
- if (macroId != undefined){
- let macro = game.macros._source.find(p => p._id == macroId);
- if (macro != undefined) {
- if (displayName) name += macro.name;
- if (displayIcon) src += macro.img;
+ if (macroId != undefined){
+ let macro = game.macros._source.find(p => p._id == macroId);
+
+ if (macro != undefined) {
+ if (displayName) name = macro.name;
+ if (displayIcon) src = macro.img;
+ if (MODULE.hotbarUses && displayUses) {
+ const uses = await this.getUses(macro);
+ if (uses != null){
+ name += '\n(' + uses.available;
+ if (uses.maximum != undefined) name += '/' + uses.maximum;
+ name += ')';
+ }
}
}
}
+
+
streamDeck.setIcon(context,src,background,ring,ringColor);
streamDeck.setTitle(name,context);
}
- hotbar(macros){
- for (let i=0; i<32; i++){
+ async getUses(macro) {
+ let hbUses = await import('../../illandril-hotbar-uses/scripts/item-system.js');
+ const command = macro.command;
+ const uses = await hbUses.calculateUses(command);
+ return uses;
+ }
+
+ async hotbar(macros){
+ for (let i=0; i<32; i++){
const data = streamDeck.buttonContext[i];
if (data == undefined || data.action != 'macro' || data.settings.macroMode == 'macroBoard') continue;
@@ -99,10 +115,16 @@ export class MacroControl{
const mode = data.settings.macroMode ? data.settings.macroMode : 'hotbar';
const displayName = data.settings.displayName ? data.settings.displayName : false;
const displayIcon = data.settings.displayIcon ? data.settings.displayIcon : false;
+ const displayUses = data.settings.displayUses ? data.settings.displayUses : false;
let background = data.settings.background ? data.settings.background : '#000000';
let macroNumber = data.settings.macroNumber;
if(macroNumber == undefined || isNaN(parseInt(macroNumber))) macroNumber = 1;
-
+
+ if ((MODULE.getPermission('MACRO','HOTBAR') == false )) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
let src = "";
let name = "";
@@ -126,6 +148,14 @@ export class MacroControl{
if (macro != undefined && macro != null) {
if (displayName) name += macro.name;
if (displayIcon) src += macro.img;
+ if (MODULE.hotbarUses && displayUses) {
+ const uses = await this.getUses(macro);
+ if (uses != null){
+ name += '\n(' + uses.available;
+ if (uses.maximum != undefined) name += '/' + uses.maximum;
+ name += ')';
+ }
+ }
}
streamDeck.setIcon(context,src,background);
streamDeck.setTitle(name,context);
@@ -137,9 +167,12 @@ export class MacroControl{
let macroNumber = settings.macroNumber;
if(macroNumber == undefined || isNaN(parseInt(macroNumber))) macroNumber = 0;
- if (mode == 'hotbar' || mode == 'visibleHotbar' || mode == 'customHotbar')
+ if (mode == 'hotbar' || mode == 'visibleHotbar' || mode == 'customHotbar'){
+ if ((MODULE.getPermission('MACRO','HOTBAR') == false )) return;
this.executeHotbar(macroNumber,mode);
+ }
else {
+ if ((MODULE.getPermission('MACRO','MACROBOARD') == false )) return;
if (settings.macroBoardMode == 'offset') {
let macroOffset = settings.macroOffset;
if (macroOffset == undefined) macroOffset = 0;
@@ -199,14 +232,4 @@ export class MacroControl{
}
}
}
-}
-
-
-
-
-
-
-
-
-
-
+}
\ No newline at end of file
diff --git a/src/misc.js b/src/misc.js
index 97ad47b..aef0cc6 100644
--- a/src/misc.js
+++ b/src/misc.js
@@ -25,6 +25,10 @@ export class playlistConfigForm extends FormApplication {
* Provide data to the template
*/
getData() {
+ if (MODULE.getPermission('PLAYLIST','CONFIGURE') == false ) {
+ ui.notifications.warn(game.i18n.localize("MaterialDeck.Notifications.Playlist.NoPermission"));
+ return;
+ }
//Get the playlist settings
let settings = game.settings.get(MODULE.moduleName,'playlists');
@@ -109,9 +113,19 @@ export class playlistConfigForm extends FormApplication {
}
async updateSettings(settings,render){
- await game.settings.set(MODULE.moduleName,'playlists', settings);
- if (MODULE.enableModule) playlistControl.updateAll();
- if (render) this.render();
+ if (game.user.isGM) {
+ await game.settings.set(MODULE.moduleName,'playlists', settings);
+ if (MODULE.enableModule) playlistControl.updateAll();
+ if (render) this.render();
+ }
+ else {
+ const payload = {
+ "msgType": "playlistUpdate",
+ "settings": settings,
+ "render": render
+ };
+ game.socket.emit(`module.MaterialDeck`, payload);
+ }
}
}
@@ -139,6 +153,10 @@ export class macroConfigForm extends FormApplication {
* Provide data to the template
*/
getData() {
+ if (MODULE.getPermission('MACRO','MACROBOARD_CONFIGURE') == false ) {
+ ui.notifications.warn(game.i18n.localize("MaterialDeck.Notifications.Macroboard.NoPermission"));
+ return;
+ }
//Get the settings
var selectedMacros = game.settings.get(MODULE.moduleName,'macroSettings').macros;
var color = game.settings.get(MODULE.moduleName,'macroSettings').color;
@@ -252,8 +270,17 @@ export class macroConfigForm extends FormApplication {
}
async updateSettings(settings){
- await game.settings.set(MODULE.moduleName,'macroSettings',settings);
- if (MODULE.enableModule) macroControl.updateAll();
+ if (game.user.isGM) {
+ await game.settings.set(MODULE.moduleName,'macroSettings',settings);
+ if (MODULE.enableModule) macroControl.updateAll();
+ }
+ else {
+ const payload = {
+ "msgType": "macroboardUpdate",
+ "settings": settings
+ };
+ game.socket.emit(`module.MaterialDeck`, payload);
+ }
}
}
@@ -285,6 +312,11 @@ export class soundboardConfigForm extends FormApplication {
* Provide data to the template
*/
getData() {
+ if (MODULE.getPermission('SOUNDBOARD','CONFIGURE') == false ) {
+ ui.notifications.warn(game.i18n.localize("MaterialDeck.Notifications.Soundboard.NoPermission"));
+ return;
+ }
+
//Get the settings
this.settings = game.settings.get(MODULE.moduleName,'soundboardSettings');
@@ -530,7 +562,16 @@ export class soundboardConfigForm extends FormApplication {
}
async updateSettings(settings){
- await game.settings.set(MODULE.moduleName,'soundboardSettings',settings);
- if (MODULE.enableModule) soundboard.updateAll();
+ if (game.user.isGM) {
+ await game.settings.set(MODULE.moduleName,'soundboardSettings',settings);
+ if (MODULE.enableModule) soundboard.updateAll();
+ }
+ else {
+ const payload = {
+ "msgType": "soundboardUpdate",
+ "settings": settings
+ };
+ game.socket.emit(`module.MaterialDeck`, payload);
+ }
}
}
\ No newline at end of file
diff --git a/src/move.js b/src/move.js
index b47f5db..bced9cd 100644
--- a/src/move.js
+++ b/src/move.js
@@ -11,6 +11,11 @@ export class Move{
const mode = settings.mode ? settings.mode : 'canvas';
const type = settings.type ? settings.type : 'move';
+ if ((MODULE.getPermission('MOVE','TOKEN') == false && mode == 'selectedToken') || (MODULE.getPermission('MOVE','CANVAS') == false && mode == 'canvas')) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
let url = '';
if (mode == 'canvas' || (mode == 'selectedToken' && type == 'move')){
const dir = settings.dir ? settings.dir : 'center';
@@ -45,14 +50,21 @@ export class Move{
url = "modules/MaterialDeck/img/move/rotateccw.png";
}
streamDeck.setIcon(context,url,background);
+ streamDeck.setTitle('',context);
}
keyPress(settings){
if (canvas.scene == null) return;
+
const dir = settings.dir ? settings.dir : 'center';
const mode = settings.mode ? settings.mode : 'canvas';
const type = settings.type ? settings.type : 'move';
+ if ((MODULE.getPermission('MOVE','TOKEN') == false && mode == 'selectedToken') || (MODULE.getPermission('MOVE','CANVAS') == false && mode == 'canvas')) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
if (type == 'move'){
if (dir == 'zoomIn') {//zoom in
let viewPosition = canvas.scene._viewPosition;
diff --git a/src/othercontrols.js b/src/othercontrols.js
index cce6c75..ad0002d 100644
--- a/src/othercontrols.js
+++ b/src/othercontrols.js
@@ -66,6 +66,11 @@ export class OtherControls{
//////////////////////////////////////////////////////////////////////////////////////////////////
updatePause(settings,context){
+ if (MODULE.getPermission('OTHER','PAUSE') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
let src = "";
const pauseFunction = settings.pauseFunction ? settings.pauseFunction : 'pause';
const background = settings.background ? settings.background : '#000000';
@@ -82,9 +87,12 @@ export class OtherControls{
else if (pauseFunction == 'toggle') //toggle
src = 'modules/MaterialDeck/img/other/pause/playpause.png';
streamDeck.setIcon(context,src,background,2,ringColor,true);
+ streamDeck.setTitle('',context);
}
keyPressPause(settings){
+ if (MODULE.getPermission('OTHER','PAUSE') == false ) return;
+
const pauseFunction = settings.pauseFunction ? settings.pauseFunction : 'pause';
if (pauseFunction == 'pause'){ //Pause game
@@ -103,6 +111,10 @@ export class OtherControls{
//////////////////////////////////////////////////////////////////////////////////////////
updateControl(settings,context){
+ if (MODULE.getPermission('OTHER','CONTROL') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const control = settings.control ? settings.control : 'dispControls';
const tool = settings.tool ? settings.tool : 'open';
let background = settings.background ? settings.background : '#000000';
@@ -118,6 +130,10 @@ export class OtherControls{
controlNr--;
const selectedControl = ui.controls.controls[controlNr];
+ if (selectedControl.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
if (selectedControl != undefined){
if (tool == 'open'){ //open category
txt = game.i18n.localize(selectedControl.title);
@@ -136,6 +152,10 @@ export class OtherControls{
if (selectedControl != undefined){
const selectedTool = selectedControl.tools[controlNr];
if (selectedTool != undefined){
+ if (selectedControl.visible == false || selectedTool.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
txt = game.i18n.localize(selectedTool.title);
src = selectedTool.icon;
if (selectedTool.toggle){
@@ -150,6 +170,10 @@ export class OtherControls{
else { // specific control/tool
const selectedControl = ui.controls.controls.find(c => c.name == control);
if (selectedControl != undefined){
+ if (selectedControl.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
if (tool == 'open'){ //open category
txt = game.i18n.localize(selectedControl.title);
src = selectedControl.icon;
@@ -159,6 +183,10 @@ export class OtherControls{
else {
const selectedTool = selectedControl.tools.find(t => t.name == tool);
if (selectedTool != undefined){
+ if (selectedTool.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
txt = game.i18n.localize(selectedTool.title);
src = selectedTool.icon;
if (selectedTool.toggle){
@@ -176,6 +204,7 @@ export class OtherControls{
}
keyPressControl(settings){
+ if (MODULE.getPermission('OTHER','CONTROL') == false ) return;
if (canvas.scene == null) return;
const control = settings.control ? settings.control : 'dispControls';
const tool = settings.tool ? settings.tool : 'open';
@@ -186,6 +215,10 @@ export class OtherControls{
controlNr--;
const selectedControl = ui.controls.controls[controlNr];
if (selectedControl != undefined){
+ if (selectedControl.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
ui.controls.activeControl = 'token';
selectedControl.activeTool = selectedControl.activeTool;
canvas.getLayer(selectedControl.layer).activate();
@@ -197,8 +230,16 @@ export class OtherControls{
controlNr--;
const selectedControl = ui.controls.controls.find(c => c.name == ui.controls.activeControl);
if (selectedControl != undefined){
+ if (selectedControl.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
const selectedTool = selectedControl.tools[controlNr];
if (selectedTool != undefined){
+ if (selectedTool.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
if (selectedTool.toggle) {
selectedTool.active = !selectedTool.active;
selectedTool.onClick(selectedTool.active);
@@ -214,6 +255,10 @@ export class OtherControls{
else { //select control
const selectedControl = ui.controls.controls.find(c => c.name == control);
if (selectedControl != undefined){
+ if (selectedControl.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
if (tool == 'open'){ //open category
ui.controls.activeControl = 'token';
selectedControl.activeTool = selectedControl.activeTool;
@@ -222,6 +267,10 @@ export class OtherControls{
else {
const selectedTool = selectedControl.tools.find(t => t.name == tool);
if (selectedTool != undefined){
+ if (selectedTool.visible == false) {
+ streamDeck.noPermission(context,false);
+ return;
+ }
ui.controls.activeControl = control;
canvas.getLayer(selectedControl.layer).activate();
if (selectedTool.toggle) {
@@ -243,6 +292,10 @@ export class OtherControls{
//////////////////////////////////////////////////////////////////////////////////////////
updateDarkness(settings,context){
+ if (MODULE.getPermission('OTHER','DARKNESS') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const func = settings.darknessFunction ? settings.darknessFunction : 'value';
const value = parseFloat(settings.darknessValue) ? parseFloat(settings.darknessValue) : 0;
const background = settings.background ? settings.background : '#000000';
@@ -268,6 +321,7 @@ export class OtherControls{
keyPressDarkness(settings) {
if (canvas.scene == null) return;
+ if (MODULE.getPermission('OTHER','DARKNESS') == false ) return;
const func = settings.darknessFunction ? settings.darknessFunction : 'value';
const value = parseFloat(settings.darknessValue) ? parseFloat(settings.darknessValue) : 0;
@@ -284,6 +338,10 @@ export class OtherControls{
//////////////////////////////////////////////////////////////////////////////////////////
updateRollDice(settings,context){
+ if (MODULE.getPermission('OTHER','DICE') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const background = settings.background ? settings.background : '#000000';
let txt = '';
@@ -294,6 +352,7 @@ export class OtherControls{
}
keyPressRollDice(settings,context){
+ if (MODULE.getPermission('OTHER','DICE') == false ) return;
if (settings.rollDiceFormula == undefined || settings.rollDiceFormula == '') return;
const rollFunction = settings.rollDiceFunction ? settings.rollDiceFunction : 'public';
@@ -333,9 +392,14 @@ export class OtherControls{
updateRollTable(settings,context){
const name = settings.rollTableName;
if (name == undefined) return;
+ if (MODULE.getPermission('OTHER','TABLES') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const background = settings.background ? settings.background : '#000000';
const table = game.tables.entities.find(p=>p.name == name);
+
let txt = settings.displayRollName ? table.name : '';
let src = settings.displayRollIcon ? table.data.img : '';
@@ -343,12 +407,19 @@ export class OtherControls{
src = '';
txt = '';
}
+ else {
+ if (table.permission < 2 && MODULE.getPermission('OTHER','TABLES_ALL') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ }
streamDeck.setTitle(txt,context);
streamDeck.setIcon(context,src,background);
}
keyPressRollTable(settings){
+ if (MODULE.getPermission('OTHER','TABLES') == false ) return;
const name = settings.rollTableName;
if (name == undefined) return;
@@ -356,6 +427,7 @@ export class OtherControls{
const table = game.tables.entities.find(p=>p.name == name);
if (table != undefined) {
+ if (table.permission < 2 && MODULE.getPermission('OTHER','TABLES_ALL') == false ) return;
if (func == 'open'){ //open
const element = document.getElementById(table.sheet.id);
if (element == null) table.sheet.render(true);
@@ -403,6 +475,10 @@ export class OtherControls{
}
updateSidebar(settings,context){
+ if (MODULE.getPermission('OTHER','SIDEBAR') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const sidebarTab = settings.sidebarTab ? settings.sidebarTab : 'chat';
const background = settings.background ? settings.background : '#000000';
const collapsed = ui.sidebar._collapsed;
@@ -417,6 +493,7 @@ export class OtherControls{
}
keyPressSidebar(settings){
+ if (MODULE.getPermission('OTHER','SIDEBAR') == false ) return;
const sidebarTab = settings.sidebarTab ? settings.sidebarTab : 'chat';
if (sidebarTab == 'collapse'){
@@ -432,10 +509,17 @@ export class OtherControls{
updateCompendium(settings,context){
const name = settings.compendiumName;
if (name == undefined) return;
+ if (MODULE.getPermission('OTHER','COMPENDIUM') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const compendium = game.packs.entries.find(p=>p.metadata.label == name);
if (compendium == undefined) return;
-
+ if (compendium.private && MODULE.getPermission('OTHER','COMPENDIUM_ALL') == false) {
+ streamDeck.noPermission(context);
+ return;
+ }
const background = settings.background ? settings.background : '#000000';
const ringOffColor = settings.offRing ? settings.offRing : '#000000';
const ringOnColor = settings.onRing ? settings.onRing : '#00FF00';
@@ -449,25 +533,33 @@ export class OtherControls{
keyPressCompendium(settings){
let name = settings.compendiumName;
if (name == undefined) return;
+ if (MODULE.getPermission('OTHER','COMPENDIUM') == false ) return;
const compendium = game.packs.entries.find(p=>p.metadata.label == name);
if (compendium == undefined) return;
+ if (compendium.private && MODULE.getPermission('OTHER','COMPENDIUM_ALL') == false) return;
if (compendium.rendered) compendium.close();
else compendium.render(true);
}
//////////////////////////////////////////////////////////////////////////////////////////
- //Journals
- //game.journal.entries[0].render(true)
-
updateJournal(settings,context){
const name = settings.compendiumName;
if (name == undefined) return;
- const journal = game.journal.entries.find(p=>p.name == name);
+ const journal = game.journal.getName(name);
if (journal == undefined) return;
+ if (MODULE.getPermission('OTHER','JOURNAL') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ if (journal.permission < 2 && MODULE.getPermission('OTHER','JOURNAL_ALL') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+
const background = settings.background ? settings.background : '#000000';
const ringOffColor = settings.offRing ? settings.offRing : '#000000';
const ringOnColor = settings.onRing ? settings.onRing : '#00FF00';
@@ -482,9 +574,12 @@ export class OtherControls{
const name = settings.compendiumName;
if (name == undefined) return;
- const journal = game.journal.entries.find(p=>p.name == name);
+ const journal = game.journal.getName(name);
if (journal == undefined) return;
+ if (MODULE.getPermission('OTHER','JOURNAL') == false ) return;
+ if (journal.permission < 2 && MODULE.getPermission('OTHER','JOURNAL_ALL') == false ) return;
+
const element = document.getElementById("journal-"+journal.id);
if (element == null) journal.render(true);
else journal.sheet.close();
@@ -493,12 +588,17 @@ export class OtherControls{
//////////////////////////////////////////////////////////////////////////////////////////
updateChatMessage(settings,context){
+ if (MODULE.getPermission('OTHER','CHAT') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const background = settings.background ? settings.background : '#000000';
streamDeck.setTitle("",context);
streamDeck.setIcon(context,"",background);
}
keyPressChatMessage(settings){
+ if (MODULE.getPermission('OTHER','CHAT') == false ) return;
const message = settings.chatMessage ? settings.chatMessage : '';
let chatData = {
diff --git a/src/playlist.js b/src/playlist.js
index f868003..a8033c1 100644
--- a/src/playlist.js
+++ b/src/playlist.js
@@ -18,6 +18,10 @@ export class PlaylistControl{
}
update(settings,context){
+ if (MODULE.getPermission('PLAYLIST','PLAY') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
this.active = true;
if (settings.playlistMode == undefined) settings.playlistMode = 'playlist';
if (settings.playlistMode == 'playlist'){
@@ -131,6 +135,14 @@ export class PlaylistControl{
}
stopAll(force=false){
+ if (game.user.isGM == false) {
+ const payload = {
+ "msgType": "stopAllPlaylists",
+ "force": force
+ };
+ game.socket.emit(`module.MaterialDeck`, payload);
+ return;
+ }
if (force){
let playing = game.playlists.playing;
for (let i=0; i p._id == selectedPlaylists[num]);
+ return game.playlists.get(selectedPlaylists[num]);
else return undefined;
}
keyPress(settings,context){
+ if (MODULE.getPermission('PLAYLIST','PLAY') == false ) return;
let playlistNr = settings.playlistNr;
if (playlistNr == undefined || playlistNr < 1) playlistNr = 1;
playlistNr--;
@@ -201,6 +214,15 @@ export class PlaylistControl{
}
async playPlaylist(playlist,playlistNr){
+ if (game.user.isGM == false) {
+ const payload = {
+ "msgType": "playPlaylist",
+ "playlistId": playlist.id,
+ "playlistNr": playlistNr
+ };
+ game.socket.emit(`module.MaterialDeck`, payload);
+ return;
+ }
if (playlist.playing) {
playlist.stopAll();
return;
@@ -214,6 +236,16 @@ export class PlaylistControl{
}
async playTrack(track,playlist,playlistNr){
+ if (game.user.isGM == false) {
+ const payload = {
+ "msgType": "playTrack",
+ "playlistId": playlist.id,
+ "playlistNr": playlistNr,
+ "trackId": track._id
+ };
+ game.socket.emit(`module.MaterialDeck`, payload);
+ return;
+ }
let play;
if (track.playing)
play = false;
diff --git a/src/scene.js b/src/scene.js
index a49a1e8..bbcbcdf 100644
--- a/src/scene.js
+++ b/src/scene.js
@@ -30,6 +30,10 @@ export class SceneControl{
let src = "";
let name = "";
if (func == 'visible') { //visible scenes
+ if (MODULE.getPermission('SCENE','VISIBLE') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
let nr = parseInt(settings.sceneNr);
if (isNaN(nr) || nr < 1) nr = 1;
nr--;
@@ -44,6 +48,10 @@ export class SceneControl{
}
}
else if (func == 'dir') { //from directory
+ if (MODULE.getPermission('SCENE','DIRECTORY') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
let nr = parseInt(settings.sceneNr);
if (isNaN(nr) || nr < 1) nr = 1;
nr--;
@@ -75,8 +83,13 @@ export class SceneControl{
}
}
else if (func == 'any') { //by name
+ if (MODULE.getPermission('SCENE','NAME') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (settings.sceneName == undefined || settings.sceneName == '') return;
- let scene = game.scenes.apps[1].entities.find(p=>p.data.name == settings.sceneName);
+ let scene = game.scenes.getName(settings.sceneName);
+
if (scene != undefined){
ringColor = scene.isView ? ringOnColor : ringOffColor;
if (settings.displaySceneName) name = scene.name;
@@ -85,6 +98,10 @@ export class SceneControl{
}
}
else if (func == 'active'){
+ if (MODULE.getPermission('SCENE','ACTIVE') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
const scene = game.scenes.active;
if (scene == undefined) return;
if (settings.displaySceneName) name = scene.name;
@@ -105,6 +122,7 @@ export class SceneControl{
const func = settings.sceneFunction ? settings.sceneFunction : 'visible';
if (func == 'visible'){ //visible scenes
+ if (MODULE.getPermission('SCENE','VISIBLE') == false ) return;
const viewFunc = settings.sceneViewFunction ? settings.sceneViewFunction : 'view';
let nr = parseInt(settings.sceneNr);
if (isNaN(nr) || nr < 1) nr = 1;
@@ -125,6 +143,7 @@ export class SceneControl{
}
}
else if (func == 'dir') { //from directory
+ if (MODULE.getPermission('SCENE','DIRECTORY') == false ) return;
const viewFunc = settings.sceneViewFunction ? settings.sceneViewFunction : 'view';
let nr = parseInt(settings.sceneNr);
if (isNaN(nr) || nr < 1) nr = 1;
@@ -156,9 +175,10 @@ export class SceneControl{
}
else if (func == 'any'){ //by name
+ if (MODULE.getPermission('SCENE','NAME') == false ) return;
if (settings.sceneName == undefined || settings.sceneName == '') return;
const scenes = game.scenes.entries;
- let scene = game.scenes.apps[1].entities.find(p=>p.data.name == settings.sceneName);
+ let scene = game.scenes.getName(settings.sceneName);
if (scene == undefined) return;
const viewFunc = settings.sceneViewFunction ? settings.sceneViewFunction : 'view';
@@ -175,6 +195,7 @@ export class SceneControl{
}
}
else if (func == 'active'){
+ if (MODULE.getPermission('SCENE','ACTIVE') == false ) return;
const scene = game.scenes.active;
if (scene == undefined) return;
scene.view();
diff --git a/src/settings.js b/src/settings.js
index 9003812..b85a74c 100644
--- a/src/settings.js
+++ b/src/settings.js
@@ -1,15 +1,79 @@
import * as MODULE from "../MaterialDeck.js";
import { playlistConfigForm, macroConfigForm, soundboardConfigForm } from "./misc.js";
+let userPermissions = {};
+const defaultEnable = [true,true,true,true];
+const defaultUserPermissions = {
+ COMBAT: {
+ END_TURN: [true,true,true,true],
+ TURN_DISPLAY: [true,true,true,true],
+ OTHER_FUNCTIONS: [false,false,true,true],
+ DISPLAY_COMBATANTS: [false,false,true,true],
+ DISPLAY_NON_OWNED_STATS: [false,false,true,true],
+ DISPLAY_LIMITED_HP: [false,true,true,true],
+ DISPLAY_OBSERVER_HP: [true,true,true,true],
+ DISPLAY_ALL_NAMES: [false,false,true,true],
+ DISPLAY_LIMITED_NAME: [false,true,true,true],
+ DISPLAY_OBSERVER_NAME: [true,true,true,true]
+ },
+ MACRO: {
+ HOTBAR: [true,true,true,true],
+ MACROBOARD: [false,false,true,true],
+ MACROBOARD_CONFIGURE: [false,false,true,true]
+ },
+ MOVE: {
+ TOKEN: [true,true,true,true],
+ CANVAS: [true,true,true,true]
+ },
+ OTHER: {
+ PAUSE: [false,false,true,true],
+ CONTROL: [true,true,true,true],
+ DARKNESS: [false,false,true,true],
+ DICE: [true,true,true,true],
+ TABLES_ALL: [false,false,true,true],
+ TABLES: [false,true,true,true],
+ SIDEBAR: [true,true,true,true],
+ COMPENDIUM_ALL: [false,false,true,true],
+ COMPENDIUM: [false,true,true,true],
+ JOURNAL_ALL: [false,false,true,true],
+ JOURNAL: [false,true,true,true],
+ CHAT: [false,true,true,true]
+ },
+ PLAYLIST: {
+ PLAY: [false,false,true,true],
+ CONFIGURE: [false,false,true,true]
+ },
+ SCENE: {
+ VISIBLE: [false,false,true,true],
+ ACTIVE: [true,true,true,true],
+ DIRECTORY: [false,false,true,true],
+ NAME: [false,false,true,true]
+ },
+ SOUNDBOARD: {
+ PLAY: [false,false,true,true],
+ CONFIGURE: [false,false,true,true]
+ },
+ TOKEN: {
+ STATS: [true,true,true,true],
+ VISIBILITY: [false,false,true,true],
+ COMBAT: [false,true,true,true],
+ VISION: [false,true,true,true],
+ WILDCARD: [false,true,true,true],
+ CONDITIONS: [false,true,true,true],
+ CUSTOM: [false,false,true,true]
+ }
+}
+
export const registerSettings = function() {
/**
* Main settings
*/
+ //world,global,client
//Enabled the module
game.settings.register(MODULE.moduleName,'Enable', {
name: "MaterialDeck.Sett.Enable",
- scope: "global",
+ scope: "client",
config: true,
default: true,
type: Boolean,
@@ -19,7 +83,7 @@ export const registerSettings = function() {
game.settings.register(MODULE.moduleName,'streamDeckModel', {
name: "MaterialDeck.Sett.Model",
hint: "MaterialDeck.Sett.Model_Hint",
- scope: "world",
+ scope: "client",
config: true,
type:Number,
default:1,
@@ -32,7 +96,7 @@ export const registerSettings = function() {
game.settings.register(MODULE.moduleName,'address', {
name: "MaterialDeck.Sett.ServerAddr",
hint: "MaterialDeck.Sett.ServerAddrHint",
- scope: "world",
+ scope: "client",
config: true,
default: "localhost:3001",
type: String,
@@ -42,9 +106,9 @@ export const registerSettings = function() {
game.settings.register(MODULE.moduleName, 'imageBuffer', {
name: "MaterialDeck.Sett.ImageBuffer",
hint: "MaterialDeck.Sett.ImageBufferHint",
- default: 0,
+ default: 100,
type: Number,
- scope: 'world',
+ scope: 'client',
range: { min: 0, max: 500, step: 10 },
config: true
@@ -55,8 +119,23 @@ export const registerSettings = function() {
name: "MaterialDeck.Sett.Help",
label: "MaterialDeck.Sett.Help",
type: helpMenu,
+ restricted: false
+ });
+
+ game.settings.registerMenu(MODULE.moduleName, 'permissionConfig',{
+ name: "MaterialDeck.Sett.Permission",
+ label: "MaterialDeck.Sett.Permission",
+ type: userPermission,
restricted: true
});
+
+ game.settings.register(MODULE.moduleName, 'userPermission', {
+ name: "userPermission",
+ scope: "world",
+ type: Object,
+ config: false
+ });
+
/**
* Playlist soundboard
*/
@@ -64,7 +143,7 @@ export const registerSettings = function() {
name: "MaterialDeck.Sett.PlaylistConfig",
label: "MaterialDeck.Sett.PlaylistConfig",
type: playlistConfigForm,
- restricted: true
+ restricted: false
});
game.settings.register(MODULE.moduleName, 'playlists', {
@@ -82,7 +161,7 @@ export const registerSettings = function() {
name: "MaterialDeck.Sett.MacroConfig",
label: "MaterialDeck.Sett.MacroConfig",
type: macroConfigForm,
- restricted: true
+ restricted: false
});
game.settings.register(MODULE.moduleName, 'macroSettings', {
@@ -114,7 +193,7 @@ export const registerSettings = function() {
name: "MaterialDeck.Sett.SoundboardConfig",
label: "MaterialDeck.Sett.SoundboardConfig",
type: soundboardConfigForm,
- restricted: true
+ restricted: false
});
}
@@ -159,3 +238,108 @@ export class helpMenu extends FormApplication {
}
}
+
+ class userPermission extends FormApplication {
+ constructor(data, options) {
+ super(data, options);
+ }
+
+ /**
+ * Default Options for this FormApplication
+ */
+ static get defaultOptions() {
+ return mergeObject(super.defaultOptions, {
+ id: "userPermissionConfig",
+ title: "Material Deck: "+game.i18n.localize("MaterialDeck.Sett.Permission"),
+ template: "./modules/MaterialDeck/templates/userPermissionConfig.html",
+ width: 660,
+ height: "auto",
+ scrollY: [".permissions-list"],
+ });
+ }
+
+ /**
+ * Provide data to the template
+ */
+ async getData() {
+ let settings = game.settings.get(MODULE.moduleName,'userPermission');
+ if (settings == undefined || settings == null || MODULE.isEmpty(settings)) {
+ settings = {
+ enable: defaultEnable,
+ permissions: defaultUserPermissions
+ }
+ }
+
+ const actions = Object.entries(duplicate(settings.permissions)).reduce((arr, e) => {
+ //const perm = e[1];
+
+ const perms = Object.entries(duplicate(e[1])).reduce((arr, p) => {
+ //const perm = e[1];
+
+ let perm = {};
+ perm.roles = [p[1][0],p[1][1],p[1][2],p[1][3]]
+ perm.id = p[0];
+ perm.label = game.i18n.localize("MaterialDeck.Perm."+e[0]+"."+p[0]+".label");
+ perm.hint = game.i18n.localize("MaterialDeck.Perm."+e[0]+"."+p[0]+".hint");
+ arr.push(perm);
+ return arr;
+ }, []);
+
+ let cat = {};
+ cat.permissions = perms;
+ cat.id = e[0];
+ cat.label = game.i18n.localize("MaterialDeck.Perm."+e[0]+".label");
+ cat.hint = game.i18n.localize("MaterialDeck.Perm."+e[0]+".hint");
+ arr.push(cat);
+ return arr;
+ }, []);
+
+ const current = await game.settings.get("core", "permissions");
+ return {
+ roles: Object.keys(CONST.USER_ROLES).reduce((obj, r) => {
+ if ( r === "NONE" ) return obj;
+ obj[r] = `USER.Role${r.titleCase()}`;
+ return obj;
+ }, {}),
+ actions: actions,
+ enable: settings.enable
+ }
+ }
+
+ /**
+ * Update on form submit
+ * @param {*} event
+ * @param {*} formData
+ */
+ async _updateObject(event, formData) {
+ let permissions = expandObject(formData);
+ let settings = {};
+ settings.enable = permissions.ENABLE;
+ delete permissions.ENABLE;
+ settings.permissions = permissions;
+ game.settings.set(MODULE.moduleName,'userPermission',settings);
+ }
+
+ async activateListeners(html) {
+ super.activateListeners(html);
+ const defaultBtn = html.find('button[name="reset"]');
+
+ defaultBtn.on("click", event => {
+ this.resetToDefault();
+ })
+
+
+ }
+
+ async resetToDefault(){
+ const settings = {
+ enable: defaultEnable,
+ permissions: defaultUserPermissions
+ }
+ await game.settings.set(MODULE.moduleName,'userPermission',settings);
+ this.render();
+ ui.notifications.info(game.i18n.localize("MaterialDeck.Perm.DefaultNotification"));
+ }
+ }
+
+
diff --git a/src/soundboard.js b/src/soundboard.js
index e990b50..0cc48f2 100644
--- a/src/soundboard.js
+++ b/src/soundboard.js
@@ -20,6 +20,10 @@ export class SoundboardControl{
}
update(settings,context){
+ if (MODULE.getPermission('SOUNDBOARD','PLAY') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
this.active = true;
const mode = settings.soundboardMode ? settings.soundboardMode : 'playSound';
const background = settings.background ? settings.background : '#000000';
@@ -69,6 +73,7 @@ export class SoundboardControl{
}
keyPressDown(settings){
+ if (MODULE.getPermission('SOUNDBOARD','PLAY') == false ) return;
const mode = settings.soundboardMode ? settings.soundboardMode : 'playSound';
if (mode == 'playSound') { //Play sound
@@ -99,6 +104,7 @@ export class SoundboardControl{
}
keyPressUp(settings){
+ if (MODULE.getPermission('SOUNDBOARD','PLAY') == false ) return;
const mode = settings.soundboardMode ? settings.soundboardMode : 'playSound';
if (mode != 'playSound') return;
@@ -133,7 +139,7 @@ export class SoundboardControl{
}
else {
const soundId = soundBoardSettings.sounds[soundNr];
- const sounds = game.playlists.entities.find(p => p._id == playlistId).data.sounds;
+ const sounds = game.playlists.get(playlistId).sounds;
if (sounds == undefined) return;
const sound = sounds.find(p => p._id == soundId);
if (sound == undefined) return;
diff --git a/src/streamDeck.js b/src/streamDeck.js
index be1a0a1..8fd3a18 100644
--- a/src/streamDeck.js
+++ b/src/streamDeck.js
@@ -444,4 +444,12 @@ export class StreamDeck{
this.imageBufferCounter = 0;
this.imageBuffer = [];
}
+
+ noPermission(context,showTxt=true){
+ const url = 'modules/MaterialDeck/img/black.png';
+ const background = '#000000';
+ const txt = showTxt ? 'no\npermission' : '';
+ this.setIcon(context,url,background);
+ this.setTitle(txt,context);
+ }
}
\ No newline at end of file
diff --git a/src/token.js b/src/token.js
index 95338e6..c122d94 100644
--- a/src/token.js
+++ b/src/token.js
@@ -20,20 +20,35 @@ export class TokenControl{
const name = settings.displayName ? settings.displayName : false;
const icon = settings.displayIcon ? settings.displayIcon : false;
const background = settings.background ? settings.background : "#000000";
- const system = settings.system ? settings.system : 'dnd5e';
-
- let stats = (system == 'demonlord') ? settings.statsDemonlord : settings.stats;
- if (stats == undefined) stats = 'none';
+ let stats = settings.stats ? settings.stats : 'none';
let tokenName = "";
let txt = "";
let iconSrc = "";
let overlay = false;
+ let statsOld;
if (tokenId != undefined) {
const token = canvas.tokens.children[0].children.find(p => p.id == tokenId);
tokenName = token.data.name;
if (name) txt += tokenName;
if (name && stats != 'none') txt += "\n";
+
+ const permission = token.actor?.permission;
+ if (settings.combat){
+ if (permission == 0 && MODULE.getPermission('COMBAT','DISPLAY_ALL_NAMES') == false) txt = "";
+ else if (permission == 1 && MODULE.getPermission('COMBAT','DISPLAY_LIMITED_NAME') == false) txt = "";
+ else if (permission == 2 && MODULE.getPermission('COMBAT','DISPLAY_OBSERVER_NAME') == false) txt = "";
+
+ if (permission == 0 && stats == 'HP') stats = 'none';
+ else if (stats == 'HP' && permission == 1 && MODULE.getPermission('COMBAT','DISPLAY_LIMITED_HP') == false) stats = 'none';
+ else if (stats == 'HP' && permission == 2 && MODULE.getPermission('COMBAT','DISPLAY_OBSERVER_HP') == false) stats = 'none';
+ else if (stats != 'HP' && permission < 3 && MODULE.getPermission('COMBAT','DISPLAY_NON_OWNED_STATS') == false) stats = 'none';
+ }
+ else if (MODULE.getPermission('TOKEN','STATS') == false) {
+ statsOld = stats;
+ stats = 'none';
+ }
+
iconSrc = token.data.img;
if (stats == 'custom'){
@@ -54,7 +69,7 @@ export class TokenControl{
}
}
}
- else if (system == 'dnd5e' && game.system.id == 'dnd5e'){
+ else if (game.system.id == 'dnd5e'){
let attributes = token.actor.data.data.attributes;
if (stats == 'HP') {
txt += attributes.hp.value + "/" + attributes.hp.max;
@@ -100,7 +115,7 @@ export class TokenControl{
else if (stats == 'PassivePerception') txt += token.actor.data.data.skills.prc.passive;
else if (stats == 'PassiveInvestigation') txt += token.actor.data.data.skills.inv.passive;
}
- else if ((system == 'dnd3.5e' && game.system.id == 'D35E') || (system == 'pf1e' && game.system.id == 'pf1')){
+ else if (game.system.id == 'D35E' || game.system.id == 'pf1'){
let attributes = token.actor.data.data.attributes;
if (stats == 'HP') txt += attributes.hp.value + "/" + attributes.hp.max;
else if (stats == 'TempHP') {
@@ -131,7 +146,7 @@ export class TokenControl{
}
else if (stats == 'Init') txt += attributes.init.total;
}
- else if (system == 'pf2e' && game.system.id == 'pf2e'){
+ else if (game.system.id == 'pf2e'){
let attributes = token.actor.data.data.attributes;
if (stats == 'HP') txt += attributes.hp.value + "/" + attributes.hp.max;
else if (stats == 'TempHP') {
@@ -155,7 +170,7 @@ export class TokenControl{
if (init != undefined) txt += init;
}
}
- else if (system == 'demonlord' && game.system.id == 'demonlord'){
+ else if (game.system.id == 'demonlord'){
let characteristics = token.actor.data.data.characteristics;
if (stats == 'HP') txt += characteristics.health.value + "/" + characteristics.health.max;
else if (stats == 'AC') txt += characteristics.defense;
@@ -171,6 +186,10 @@ export class TokenControl{
}
if (settings.onClick == 'visibility') { //toggle visibility
+ if (MODULE.getPermission('TOKEN','VISIBILITY') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
ring = 1;
if (token.data.hidden){
ring = 2;
@@ -182,6 +201,10 @@ export class TokenControl{
}
}
else if (settings.onClick == 'combatState') { //toggle combat state
+ if (MODULE.getPermission('TOKEN','COMBAT') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
ring = 1;
if (token.inCombat){
ring = 2;
@@ -203,8 +226,12 @@ export class TokenControl{
}
}
else if (settings.onClick == 'condition') { //toggle condition
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
ring = 1;
- if ((system == 'dnd5e' && game.system.id == 'dnd5e') || (system == 'dnd3.5e' && game.system.id == 'D35E') || (system == 'pf1e' && game.system.id == 'pf1')){
+ if (game.system.id == 'dnd5e' || game.system.id == 'D35E' || game.system.id == 'pf1'){
const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll' && icon == false)
iconSrc = window.CONFIG.controlIcons.effects;
@@ -219,8 +246,8 @@ export class TokenControl{
}
}
}
- else if (system == 'pf2e' && game.system.id == 'pf2e') {
- const condition = settings.conditionPF2E ? settings.conditionPF2E : 'removeAll';
+ else if (game.system.id == 'pf2e') {
+ const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll' && icon == false)
iconSrc = window.CONFIG.controlIcons.effects;
else if (icon == false) {
@@ -234,8 +261,8 @@ export class TokenControl{
iconSrc = this.pf2eCondition(condition);
}
}
- else if (system == 'demonlord' && game.system.id == 'demonlord'){
- const condition = settings.conditionDemonlord ? settings.conditionDemonlord : 'removeAll';
+ else if (game.system.id == 'demonlord'){
+ const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll' && icon == false)
iconSrc = window.CONFIG.controlIcons.effects;
else if (icon == false) {
@@ -253,7 +280,31 @@ export class TokenControl{
iconSrc = "";
overlay = true;
}
+ else if (settings.onClick == 'cubCondition') { //Combat Utility Belt conditions
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ ring = 1;
+ overlay = true;
+ const condition = settings.cubConditionName;
+ if (condition == undefined || condition == '') return;
+ if (icon == false) {
+ let effect = CONFIG.statusEffects.find(e => e.label === condition);
+ iconSrc = effect.icon;
+ let effects = token.actor.effects.entries;
+ let active = effects.find(e => e.isTemporary === effect.id);
+ if (active != undefined){
+ ring = 2;
+ ringColor = "#FF7B00";
+ }
+ }
+ }
else if (settings.onClick == 'wildcard') { //wildcard images
+ if (MODULE.getPermission('TOKEN','WILDCARD') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (icon == false) return;
const method = settings.wildcardMethod ? settings.wildcardMethod : 'iterate';
let value = parseInt(settings.wildcardValue);
@@ -285,12 +336,15 @@ export class TokenControl{
}
}
else return;
-
- }
+ }
}
else {
iconSrc += "";
if (settings.onClick == 'visibility') { //toggle visibility
+ if (MODULE.getPermission('TOKEN','VISIBILITY') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (icon == false) {
iconSrc = window.CONFIG.controlIcons.visibility;
ring = 2;
@@ -298,6 +352,10 @@ export class TokenControl{
}
}
else if (settings.onClick == 'combatState') { //toggle combat state
+ if (MODULE.getPermission('TOKEN','COMBAT') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
if (icon == false) {
iconSrc = window.CONFIG.controlIcons.combat;
ring = 2;
@@ -312,22 +370,26 @@ export class TokenControl{
}
}
else if (settings.onClick == 'condition') { //toggle condition
- if ((system == 'dnd5e' && game.system.id == 'dnd5e') || (system == 'dnd3.5e' && game.system.id == 'D35E') || (system == 'pf1e' && game.system.id == 'pf1')){
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ if (game.system.id == 'dnd5e' || game.system.id == 'D35E' || game.system.id == 'pf1'){
const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll' && icon == false)
iconSrc = window.CONFIG.controlIcons.effects;
else if (icon == false)
iconSrc = CONFIG.statusEffects.find(e => e.id === condition).icon;
}
- else if (system == 'pf2e' && game.system.id == 'pf2e') {
- const condition = settings.conditionPF2E ? settings.conditionPF2E : 'removeAll';
+ else if (game.system.id == 'pf2e') {
+ const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll' && icon == false)
iconSrc = window.CONFIG.controlIcons.effects;
else if (icon == false)
iconSrc = this.pf2eCondition(condition);
}
- else if (system == 'demonlord' && game.system.id == 'demonlord'){
- const condition = settings.conditionDemonlord ? settings.conditionDemonlord : 'removeAll';
+ else if (game.system.id == 'demonlord'){
+ const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll' && icon == false)
iconSrc = window.CONFIG.controlIcons.effects;
else if (icon == false)
@@ -336,8 +398,22 @@ export class TokenControl{
ring = 1;
overlay = true;
}
+ else if (settings.onClick == 'cubCondition') { //Combat Utility Belt conditions
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
+ streamDeck.noPermission(context);
+ return;
+ }
+ const condition = settings.cubConditionName;
+ if (condition == undefined || condition == '') return;
+ if (icon == false) {
+ iconSrc = CONFIG.statusEffects.find(e => e.label === condition).icon;
+ }
+ ring = 1;
+ overlay = true;
+ }
}
if (icon == false){
+ if (MODULE.getPermission('TOKEN','STATS') == false) stats = statsOld;
if (stats == 'HP' || stats == 'TempHP') //HP
iconSrc = "modules/MaterialDeck/img/token/hp.png";
else if (stats == 'AC' || stats == 'ShieldHP') //AC
@@ -362,10 +438,7 @@ export class TokenControl{
const token = canvas.tokens.children[0].children.find(p => p.id == tokenId);
if (token == undefined) return;
- let system = settings.system ? settings.system : 'dnd5e';
-
- let onClick = (system == 'demonlord') ? settings.onClickDemonlord : settings.onClick;
- if (onClick == undefined) onClick = 'doNothing';
+ const onClick = settings.onClick ? settings.onClick : 'doNothing';
if (onClick == 'doNothing') //Do nothing
return;
@@ -384,16 +457,19 @@ export class TokenControl{
else token.sheet.close();
}
else if (onClick == 'visibility') { //Toggle visibility
+ if (MODULE.getPermission('TOKEN','VISIBILITY') == false ) return;
token.toggleVisibility();
}
else if (onClick == 'combatState') { //Toggle combat state
+ if (MODULE.getPermission('TOKEN','COMBAT') == false ) return;
token.toggleCombat();
}
else if (onClick == 'target') { //Target token
token.setTarget(!token.isTargeted,{releaseOthers:false});
}
else if (onClick == 'condition') { //Toggle condition
- if ((system == 'dnd5e' && game.system.id == 'dnd5e') || (system == 'dnd3.5e' && game.system.id == 'D35E') || (system == 'pf1e' && game.system.id == 'pf1')){
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) return;
+ if (game.system.id == 'dnd5e' || game.system.id == 'D35E' || game.system.id == 'pf1'){
const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll'){
for( let effect of token.actor.effects)
@@ -404,8 +480,8 @@ export class TokenControl{
await token.toggleEffect(effect);
}
}
- else if (system == 'pf2e' && game.system.id == 'pf2e'){
- const condition = settings.conditionPF2E ? settings.conditionPF2E : 'removeAll';
+ else if (game.system.id == 'pf2e'){
+ const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll'){
for( let effect of token.actor.effects)
await effect.delete();
@@ -415,8 +491,8 @@ export class TokenControl{
await token.toggleEffect(effect);
}
}
- else if (system == 'demonlord' && game.system.id == 'demonlord'){
- const condition = settings.conditionDemonlord ? settings.conditionDemonlord : 'removeAll';
+ else if (game.system.id == 'demonlord'){
+ const condition = settings.condition ? settings.condition : 'removeAll';
if (condition == 'removeAll'){
for( let effect of token.actor.effects)
await effect.delete();
@@ -429,7 +505,17 @@ export class TokenControl{
this.update(tokenId);
}
+ else if (settings.onClick == 'cubCondition') { //Combat Utility Belt conditions
+ if (MODULE.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 (MODULE.getPermission('TOKEN','VISION') == false ) return;
const token = canvas.tokens.children[0].children.find(p => p.id == tokenId);
if (token == undefined) return;
let tokenData = token.data;
@@ -468,13 +554,14 @@ export class TokenControl{
data.lightAnimation = animation;
token.update(data);
}
- else if (system == 'demonlord' && game.system.id == 'demonlord' && onClick == 'initiative'){
+ else if (game.system.id == 'demonlord' && onClick == 'initiative'){
token.actor.update({
'data.fastturn': !token.actor.data?.data?.fastturn
})
}
else if (onClick == 'wildcard') { //wildcard images
+ if (MODULE.getPermission('TOKEN','WILDCARD') == false ) return;
const method = settings.wildcardMethod ? settings.wildcardMethod : 'iterate';
let value = parseInt(settings.wildcardValue);
if (isNaN(value)) value = 1;
@@ -509,6 +596,7 @@ export class TokenControl{
token.update({img: iconSrc})
}
else if (onClick == 'custom') {//custom onClick function
+ if (MODULE.getPermission('TOKEN','CUSTOM') == false ) return;
const formula = settings.customOnClickFormula ? settings.customOnClickFormula : '';
if (formula == '') return;
diff --git a/templates/helpMenu.html b/templates/helpMenu.html
index b6247b6..b2b82b5 100644
--- a/templates/helpMenu.html
+++ b/templates/helpMenu.html
@@ -74,6 +74,18 @@
This improves the update speed, but increases memory usage.
+
+
+ User Permission Configuration
+
+ Using the 'User Permission Configuration' screen, the GM can configure what Material Deck functions users have access to.
+ Each action has various settings, and these settings can be set for each user role.
+
+ To save the settings, press the 'Save Configuration' button at the lower left, or to set the settings back to the default values, press 'Reset Defaults' in the lower right.
+
+
+
+
Playlist Configuration
The playlist configuration screen configures the playlists that you control using the Playlist action.
diff --git a/templates/userPermissionConfig.html b/templates/userPermissionConfig.html
new file mode 100644
index 0000000..db9aef2
--- /dev/null
+++ b/templates/userPermissionConfig.html
@@ -0,0 +1,113 @@
+
diff --git a/wiki/img/.thumb/ModuleSettings.png.jpg b/wiki/img/.thumb/ModuleSettings.png.jpg
index 8661654..3d07d35 100644
Binary files a/wiki/img/.thumb/ModuleSettings.png.jpg and b/wiki/img/.thumb/ModuleSettings.png.jpg differ
diff --git a/wiki/img/.thumb/PermissionConfig.png.jpg b/wiki/img/.thumb/PermissionConfig.png.jpg
new file mode 100644
index 0000000..9f8b81a
Binary files /dev/null and b/wiki/img/.thumb/PermissionConfig.png.jpg differ
diff --git a/wiki/img/ModuleSettings.png b/wiki/img/ModuleSettings.png
index 5b55dc6..12beed6 100644
Binary files a/wiki/img/ModuleSettings.png and b/wiki/img/ModuleSettings.png differ
diff --git a/wiki/img/PermissionConfig.png b/wiki/img/PermissionConfig.png
new file mode 100644
index 0000000..308f897
Binary files /dev/null and b/wiki/img/PermissionConfig.png differ