diff --git a/MaterialDeck.js b/MaterialDeck.js
index 7e5ea67..c546e21 100644
--- a/MaterialDeck.js
+++ b/MaterialDeck.js
@@ -32,6 +32,8 @@ let controlTokenTimer;
export let sdVersion;
export let msVersion;
+
+let updateDialog;
//CONFIG.debug.hooks = true;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -86,8 +88,8 @@ async function analyzeWSmessage(msg){
sdVersion = data.version;
- if (data.version < minimumSDversion) {
- let d = new Dialog({
+ if (data.version < minimumSDversion && updateDialog == undefined) {
+ updateDialog = new Dialog({
title: "Material Deck: Update Needed",
content: "
The Stream Deck plugin version you're using is v" + data.version + ", which is incompatible with this verion of the module.
Update to v" + minimumSDversion + " or newer.
",
buttons: {
@@ -103,7 +105,7 @@ async function analyzeWSmessage(msg){
},
default: "download"
});
- d.render(true);
+ updateDialog.render(true);
}
}
@@ -225,19 +227,22 @@ let messageCount = 0;
*/
function resetWS(){
const maxMessages = game.settings.get(moduleName, 'nrOfConnMessages');
- if (maxMessages == 0 || maxMessages > messageCount) {
- messageCount++;
- const countString = maxMessages == 0 ? "" : " (" + messageCount + "/" + maxMessages + ")";
- if (wsOpen) {
- ui.notifications.warn("Material Deck: "+game.i18n.localize("MaterialDeck.Notifications.Disconnected"));
- wsOpen = false;
- messageCount = 0;
- }
- else ui.notifications.warn("Material Deck: "+game.i18n.localize("MaterialDeck.Notifications.ConnectFail") + countString);
+ if (wsOpen) {
+ ui.notifications.warn("Material Deck: "+game.i18n.localize("MaterialDeck.Notifications.Disconnected"));
+ wsOpen = false;
+ messageCount = 0;
+ WSconnected = false;
+ startWebsocket();
+ }
+ else if (ws.readyState == 3){
+ if (maxMessages == 0 || maxMessages > messageCount) {
+ messageCount++;
+ const countString = maxMessages == 0 ? "" : " (" + messageCount + "/" + maxMessages + ")";
+ ui.notifications.warn("Material Deck: "+game.i18n.localize("MaterialDeck.Notifications.ConnectFail") + countString);
+ }
+ WSconnected = false;
+ startWebsocket();
}
-
- WSconnected = false;
- startWebsocket();
}
export function sendWS(txt){
@@ -275,8 +280,6 @@ Hooks.once('ready', async()=>{
registerSettings();
enableModule = (game.settings.get(moduleName,'Enable')) ? true : false;
-
-
soundboard = new SoundboardControl();
streamDeck = new StreamDeck();
tokenControl = new TokenControl();
@@ -365,12 +368,6 @@ Hooks.once('ready', async()=>{
}
}
-
-
-
-
-
-
if (enableModule == false) return;
if (getPermission('ENABLE') == false) {
ready = true;
@@ -472,6 +469,10 @@ Hooks.on('renderSidebarTab',(app)=>{
if (enableModule == false || ready == false) return;
if (otherControls != undefined) otherControls.updateAll(options);
if (sceneControl != undefined) sceneControl.updateAll();
+ if (document.getElementsByClassName("roll-type-select")[0] != undefined)
+ document.getElementsByClassName("roll-type-select")[0].addEventListener('change',function(){
+ if (otherControls != undefined) otherControls.updateAll(options);
+ })
});
Hooks.on('closeSidebarTab',(app)=>{
@@ -578,4 +579,8 @@ Hooks.once('init', ()=>{
Hooks.once('canvasReady',()=>{
ready = true;
+});
+
+Hooks.on("soundscape", (data) => {
+ externalModules.newSoundscapeData(data);
});
\ No newline at end of file
diff --git a/README.md b/README.md
index a4bec5d..931e2b8 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
I created a Discord server to discuss this and other hardware-based Foundry modules. Feel free to join if you'd like to join the discussion and be updated on this module.
-[](https://youtu.be/7h5Ew8cJYxg "FoundryVTT Material Deck Demonstration")
+[](https://youtu.be/7h5Ew8cJYxg "FoundryVTT Material Deck Demonstration")
# Material Deck
Material Deck is a Foundry VTT module that allows you to control certain Foundry functions using an Elgato Stream Deck. A Stream Deck is a device that has physical buttons with displays behind them. Material Deck uses this to, for example, control playlists, execute macros, display and control the combat tracker.
diff --git a/changelog.md b/changelog.md
index bc96dd0..c2af909 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,4 +1,28 @@
# Changelog Material Deck Module
+### v1.4.5 - 27-07-2021
+Fixes:
+
+- Combat Tracker Action => Turn Display: If only 'Display Round' was enabled, the vertical alignment would be off. This has been fixed.
+- WebSocket client no longer creates duplicate connections
+- Token Action => If 'Display Uses/Quantity' is enabled for an item that has no maximum uses/quantity, the uses/quantity border is now consistently black.
+- Update dialog that appears if the SD plugin needs to be updated now only appears once
+
+
+Additions:
+
+- External Modules: Added support for the Soundscape module. Requires Soundscape v1.0.3
+- Macro Action => Advanced Macros is now supported for calling macros with arguments
+- Combat Tracker Action => Function: Added option to select the combatant after changing the turn
+- Other Actions => Added 'Set Roll Mode' which sets the roll mode for all rolls to public, private gm, blind gm or self roll
+- Added support for wfrp4e (thanks to sozin#8622 & eccobold#3541)
+- Added DEVGUIDE.md to help developers add support for new systems (thanks to sozin#8622 & eccobold#3541)
+
+
+
+Compatible server app and SD plugin:
+Material Server v1.0.2 (unchanged): https://github.com/CDeenen/MaterialServer/releases
+SD plugin v1.4.5 (must be updated!): https://github.com/CDeenen/MaterialDeck_SD/releases
+
### v1.4.4 - 26-05-2021
Fixes:
diff --git a/module.json b/module.json
index b8511d3..3bb51ba 100644
--- a/module.json
+++ b/module.json
@@ -2,7 +2,7 @@
"name": "MaterialDeck",
"title": "Material Deck",
"description": "Material Deck allows you to control Foundry using an Elgato Stream Deck",
- "version": "1.4.4",
+ "version": "1.4.5",
"author": "CDeenen",
"authors": {
"name": "CDeenen",
@@ -22,7 +22,7 @@
],
"socket": true,
"minimumCoreVersion": "0.7.5",
- "compatibleCoreVersion": "0.8.5",
+ "compatibleCoreVersion": "0.8.8",
"languages": [
{
"lang": "en",
diff --git a/src/combattracker.js b/src/combattracker.js
index 4ee9a19..c478603 100644
--- a/src/combattracker.js
+++ b/src/combattracker.js
@@ -127,16 +127,18 @@ export class CombatTracker{
turn = combat.turn+1;
}
if (settings.displayRound) txt += "Round\n"+round;
- if (txt != "") txt += "\n";
+ if (settings.displayRound && settings.displayTurn) txt += "\n";
if (settings.displayTurn) txt += "Turn\n"+turn;
}
+
streamDeck.setIcon(context,device,src,{background:background});
streamDeck.setTitle(txt,context);
}
}
- keyPress(settings,context,device){
+ async keyPress(settings,context,device){
const mode = settings.combatTrackerMode ? settings.combatTrackerMode : 'combatants';
+ const selectCombatant = settings.selectCombatant ? settings.selectCombatant : false;
const combat = game.combat;
if (mode == 'function'){
@@ -169,11 +171,17 @@ export class CombatTracker{
}
if (game.combat.started == false) return;
- if (ctFunction == 'nextTurn') game.combat.nextTurn();
- 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();
+ if (ctFunction == 'nextTurn') await game.combat.nextTurn();
+ else if (ctFunction == 'prevTurn') await game.combat.previousTurn();
+ else if (ctFunction == 'nextRound') await game.combat.nextRound();
+ else if (ctFunction == 'prevRound') await game.combat.previousRound();
+ else if (ctFunction == 'endTurn' && game.combat.combatant.owner) await game.combat.nextTurn();
+
+ if (selectCombatant) {
+ const token = canvas.tokens.placeables.filter(token => token.id == game.combat.combatant.token.id)[0];
+ if (token.can(game.userId,"control")) token.control();
+ }
+
}
else {
const onClick = settings.onClick ? settings.onClick : 'doNothing';
diff --git a/src/external.js b/src/external.js
index ddc44e4..f1ed0d4 100644
--- a/src/external.js
+++ b/src/external.js
@@ -1,9 +1,44 @@
import {streamDeck} from "../MaterialDeck.js";
export class ExternalModules{
+ soundscapeSettings = {
+ channels: [],
+ master: {
+ mute: false,
+ volume: 1,
+ playing: false,
+ name: 'Master'
+ },
+ soundboard: [],
+ soundboardVolume: 1,
+ playing: false
+ }
constructor(){
this.active = false;
this.gmScreenOpen = false;
+
+ let channelData = [];
+ let soundboardData = [];
+ for (let i=0; i<8; i++) {
+ channelData.push({
+ volume: 1,
+ mute: false,
+ solo: false,
+ link: false,
+ playing: false,
+ pan: 1,
+ name: ''
+ })
+ }
+ for (let i=0; i<25; i++) {
+ soundboardData.push({
+ active: false,
+ name: '',
+ icon: ''
+ })
+ }
+ this.soundscapeSettings.channels = channelData;
+ this.soundscapeSettings.soundboard = soundboardData;
}
async updateAll(data={}){
@@ -33,6 +68,7 @@ export class ExternalModules{
else if (module == 'notYourTurn') this.updateNotYourTurn(settings,context,device);
else if (module == 'lockView') this.updateLockView(settings,context,device);
else if (module == 'aboutTime') this.updateAboutTime(settings,context,device);
+ else if (module == 'soundscape') this.updateSoundscape(settings,context,device);
}
keyPress(settings,context,device){
@@ -47,6 +83,7 @@ export class ExternalModules{
else if (module == 'notYourTurn') this.keyPressNotYourTurn(settings,context,device);
else if (module == 'lockView') this.keyPressLockView(settings,context,device);
else if (module == 'aboutTime') this.keyPressAboutTime(settings,context,device);
+ else if (module == 'soundscape') this.keyPressSoundscape(settings,context,device);
}
getModuleEnable(moduleId){
@@ -698,6 +735,327 @@ export class ExternalModules{
game.Gametime.advanceTime({ hours: -1 });
}
}
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //Soundscape
+ //////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ updateSoundscape(settings,context,device) {
+
+ if (this.getModuleEnable("soundscape") == false) return;
+ if (game.user.isGM == false) return;
+
+ const target = settings.soundscapeTarget ? settings.soundscapeTarget : 'mixer';
+ let channel = settings.soundscapeChannel ? settings.soundscapeChannel : 1;
+
+ let background = '#000000';
+ let ring = 0;
+ let ringColor = '#000000';
+
+ let txt = '';
+ let src = 'modules/MaterialDeck/img/transparant.png';
+ let name = '';
+
+ if (target == 'mixer') {
+ let mode = settings.soundscapeMixerMode ? settings.soundscapeMixerMode : 'startStopAll';
+ const displayName = settings.soundscapeMixerName;
+ const displayChannel = settings.soundscapeDisplayMixerChannel;
+
+ if (mode == 'startStopAll') {
+ channel = 'master';
+ mode = 'startStop';
+ }
+
+ if (channel == 'Master') channel = 'master';
+
+ let channelSettings;
+ if (channel == 'master') {
+ channelSettings = this.soundscapeSettings.master;
+ channelSettings.playing = this.soundscapeSettings.playing;
+ channelSettings.name = 'Master';
+ }
+ else channelSettings = this.soundscapeSettings.channels[channel-1];
+ if (displayChannel && channel == 'master') txt += 'Master';
+ else if (displayChannel) txt += channel;
+
+ if (mode == 'startStop') {
+ if (displayChannel) txt += '\n';
+ if (channelSettings.playing) {
+ src = "fas fa-stop";
+ ringColor = '#00ff00';
+ }
+ else {
+ src = "fas fa-play";
+ ringColor = '#006600';
+ }
+ ring=2;
+
+ }
+ else if (mode == 'mute') {
+ if (displayChannel) txt += '\n';
+ txt += 'M';
+ if (displayName) txt += '\n';
+ ring=2;
+ ringColor = '#ff0000';
+ background = channelSettings.mute ? '#ff0000' : '#660000'
+ }
+ else if (mode == 'solo') {
+ if (channel == 'master') return;
+ if (displayChannel) txt += '\n';
+ txt += 'S';
+ if (displayName) txt += '\n';
+ ring=2;
+ ringColor = '#ffff00';
+ background = channelSettings.solo ? '#ffff00' : '#666600'
+ }
+ else if (mode == 'link') {
+ if (channel == 'master') return;
+ if (displayChannel) txt += '\n';
+ txt += 'L';
+ if (displayName) txt += '\n';
+ ring=2;
+ ringColor = '#00ffff';
+ background = channelSettings.link ? '#00ffff' : '#000066'
+ }
+ else if (displayName && displayChannel) txt += '\n';
+
+ if (displayName) txt += channelSettings.name;
+
+ if (mode == 'volume') {
+ const displayValue = settings.soundscapeDisplayMixerValue;
+ const volume = Math.floor(channelSettings.volume*100);
+ if (displayValue && (displayName || displayChannel)) txt += '\n';
+ if (displayValue) txt += volume;
+ }
+
+ }
+ else if (target == 'soundboard') {
+ const displayName = settings.soundscapeSoundboardName;
+ const displayIcon = settings.soundscapeSoundboardIcon;
+ const displayChannel = settings.soundscapeDisplayChannel;
+ const displayValue = settings.soundscapeSoundboardValue;
+ const mode = settings.soundscapeSoundboardMode ? settings.soundscapeSoundboardMode : 'play';
+
+ if (mode == 'play') {
+ channel -= 1;
+ let sound = this.soundscapeSettings.soundboard[channel];
+
+ if (displayChannel) txt += channel+1;
+ if (displayChannel && displayName) txt += '\n';
+ if (displayName) txt += sound.name;
+ if (displayIcon) src = sound.icon;
+ }
+ else if (mode == 'volume') {
+ const volume = Math.floor(this.soundscapeSettings.soundboardVolume*100);
+ if (displayValue) txt += volume;
+ }
+ else if (mode == 'stop') {
+ src = 'modules/MaterialDeck/img/playlist/stop.png';
+ }
+ }
+
+ streamDeck.setTitle(txt,context);
+ streamDeck.setIcon(context,device,src,{background:background,ring:ring,ringColor:ringColor});
+ }
+
+ async keyPressSoundscape(settings,context,device) {
+ if (this.getModuleEnable("soundscape") == false) return;
+ if (game.user.isGM == false) return;
+
+ const target = settings.soundscapeTarget ? settings.soundscapeTarget : 'mixer';
+ let channel = settings.soundscapeChannel ? settings.soundscapeChannel : 1;
+
+ if (target == 'mixer') {
+ const mode = settings.soundscapeMixerMode ? settings.soundscapeMixerMode : 'startStopAll';
+
+ if (mode == 'startStopAll') {
+ const playing = !this.soundscapeSettings.playing;
+ if (playing) {
+ Hooks.call('setSoundscape',{"msgType":"start","channelNr":undefined});
+ return;
+ }
+ else {
+ Hooks.call('setSoundscape',{"msgType":"stop","channelNr":undefined});
+ return;
+ }
+ }
+
+ if (channel == 'Master') channel = 'master';
+
+ let channelSettings;
+ if (channel == 'master') {
+ channelSettings = this.soundscapeSettings.master;
+ channelSettings.playing = this.soundscapeSettings.playing;
+ }
+ else channelSettings = this.soundscapeSettings.channels[channel-1];
+
+ let mute, solo, link, playing;
+ let setChannel = false;
+
+ if (mode == 'startStop') {
+ setChannel = true;
+ playing = !channelSettings.playing;
+ if (channel == 'master' && playing) {
+ Hooks.call('setSoundscape',{"msgType":"stop"});
+ return;
+ }
+ else if (channel == 'master') {
+ Hooks.call('setSoundscape',{"msgType":"start"});
+ return;
+ }
+ }
+ else if (mode == 'mute') {
+ setChannel = true;
+ mute = !channelSettings.mute;
+ }
+ else if (mode == 'solo') {
+ if (channel == 'master') return;
+ setChannel = true;
+ solo = !channelSettings.solo;
+ }
+ else if (mode == 'link') {
+ if (channel == 'master') return;
+ setChannel = true;
+ link = !channelSettings.link;
+ }
+ if (setChannel) {
+ const channelNr = channel == 'master' ? 'master' : channel-1;
+ const payload = {
+ "msgType": "setChannel",
+ "channelNr": channelNr,
+ mute,
+ solo,
+ link,
+ playing
+ };
+ Hooks.call('setSoundscape',payload);
+ return;
+ }
+
+ if (mode == 'volume') {
+ const volumeMode = settings.soundscapeMixerValueMode ? settings.soundscapeMixerValueMode : 'incrementDecrement';
+ const value = parseInt(settings.soundscapeMixerValue);
+ if (isNaN(value) == false) {
+ let volume = channelSettings.volume*100;
+
+ if (volumeMode == 'set')
+ volume = value;
+ else
+ volume += value;
+
+ volume = Math.floor(volume*100)/10000;
+ const channelNr = channel == 'master' ? 'master' : channel-1;
+ const payload = {
+ "msgType": "setVolume",
+ "channelNr": channelNr,
+ volume
+ };
+ Hooks.call('setSoundscape',payload);
+ }
+ }
+
+
+ }
+ else if (target == 'soundboard') {
+ const mode = settings.soundscapeSoundboardMode ? settings.soundscapeSoundboardMode : 'play';
+
+ if (mode == 'play') {
+ channel -= 1;
+ const payload = {
+ "msgType": "playSoundboard",
+ channelNr: channel
+ };
+ Hooks.call('setSoundscape',payload);
+ }
+ else if (mode == 'volume') {
+ const volumeMode = settings.soundscapeSoundboardValueMode ? settings.soundscapeSoundboardValueMode : 'incrementDecrement';
+ const value = parseInt(settings.soundscapeSoundboardValue);
+ if (isNaN(value) == false) {
+ let volume = this.soundscapeSettings.soundboardVolume*100;
+
+ if (volumeMode == 'set')
+ volume = value;
+ else
+ volume += value;
+
+ volume = Math.floor(volume*100)/10000;
+ const payload = {
+ "msgType": "setSoundboardVolume",
+ volume
+ };
+ Hooks.call('setSoundscape',payload);
+ }
+ }
+ else if (mode == 'stop') {
+ const payload = {
+ "msgType": "stopSoundboard"
+ };
+ Hooks.call('setSoundscape',payload);
+ }
+ }
+ }
+
+ newSoundscapeData(data) {
+ let channel;
+ if (data.channel != undefined) channel = data.channel;
+ else if (data.channelNr != undefined) channel = data.channelNr;
+
+ let channelSettings = channel == 'master' ? this.soundscapeSettings.master : this.soundscapeSettings.channels[channel]
+ if (data.msgType == 'soundConfig') {
+
+ let newChannelSettings = {
+ volume: data.data.settings.volume,
+ mute: data.data.settings.mute,
+ solo: data.data.settings.solo,
+ link: data.data.settings.link,
+ playing: false,
+ pan: data.data.settings.pan,
+ name: data.data.settings.name
+ };
+ this.soundscapeSettings.channels[channel] = newChannelSettings;
+ }
+ else if (data.msgType == 'setMute') channelSettings.mute = data.mute;
+ else if (data.msgType == 'setSolo') channelSettings.solo = data.solo;
+ else if (data.msgType == 'setLink') channelSettings.link = data.link;
+ else if (data.msgType == 'setVolume') {
+ if (channel >= 100) return;
+ channelSettings.volume = data.volume;
+ }
+ else if (data.msgType == 'start') {
+ this.soundscapeSettings.playing = true;
+ this.soundscapeSettings.master.playing = true;
+ if (data.channel == undefined) for (let i=0; i<8; i++) this.soundscapeSettings.channels[i].playing = true;
+ else this.soundscapeSettings.channels[data.channel].playing = true;
+ }
+ else if (data.msgType == 'stop') {
+
+ if (data.channel == undefined) {
+ for (let i=0; i<8; i++) this.soundscapeSettings.channels[i].playing = false;
+ this.soundscapeSettings.playing = false;
+ }
+ else {
+ this.soundscapeSettings.channels[data.channel].playing = false;
+ let check = 0;
+ for (let i=0; i<8; i++) if (this.soundscapeSettings.channels[data.channel].playing) check++;
+ if (check == 0) this.soundscapeSettings.playing = false;
+ }
+ }
+ else if (data.msgType == 'sbSoundConfig') {
+
+ const channel = data.channel - 100;
+ let active = true;
+ if (data.data.soundArray == undefined) active = false;
+ this.soundscapeSettings.soundboard[channel] = {
+ active,
+ name: data.data.name,
+ icon: data.data.imageSrc
+ };
+ }
+ else if (data.msgType == 'setSoundboardVolume')
+ this.soundscapeSettings.soundboardVolume = data.volume;
+
+ this.updateAll();
+ }
}
diff --git a/src/macro.js b/src/macro.js
index 743992f..3e12fd5 100644
--- a/src/macro.js
+++ b/src/macro.js
@@ -180,7 +180,12 @@ export class MacroControl{
let furnaceEnabled = false;
let furnace = game.modules.get("furnace");
if (furnace != undefined && furnace.active && compatibleCore("0.8.1")==false) furnaceEnabled = true;
+
+ let advancedMacros = game.modules.get("advanced-macros");
+ if (advancedMacros != undefined && advancedMacros.active) furnaceEnabled = true;
+
if (args == "" || args == " ") furnaceEnabled = false;
+
if (furnaceEnabled == false) macro.execute({token:target});
else {
let chatData = {
@@ -235,7 +240,12 @@ export class MacroControl{
let furnaceEnabled = false;
let furnace = game.modules.get("furnace");
if (furnace != undefined && furnace.active && compatibleCore("0.8.1")==false) furnaceEnabled = true;
+
+ let advancedMacros = game.modules.get("advanced-macros");
+ if (advancedMacros != undefined && advancedMacros.active) furnaceEnabled = true;
+
if (args == undefined || args[macroNumber] == undefined || args[macroNumber] == "") furnaceEnabled = false;
+
if (furnaceEnabled == false) macro.execute();
else {
let chatData = {
diff --git a/src/misc.js b/src/misc.js
index fc639f8..3d27cfd 100644
--- a/src/misc.js
+++ b/src/misc.js
@@ -183,7 +183,8 @@ export class macroConfigForm extends FormApplication {
let furnaceEnabled = false;
let height = 95;
let furnace = game.modules.get("furnace");
- if (furnace != undefined && furnace.active && compatibleCore("0.8.1")==false) {
+ let advancedMacros = game.modules.get("advanced-macros");
+ if ((furnace != undefined && furnace.active && compatibleCore("0.8.1")==false) || (advancedMacros != undefined && advancedMacros.active)) {
furnaceEnabled = true;
height += 50;
}
@@ -1056,12 +1057,23 @@ export class downloadUtility extends FormApplication {
}
if (this.localMSversion == undefined) this.localMSversion = 'unknown';
+ let minimumSdVersion;
+ let minimumMsVersion;
+ if (compatibleCore("0.8.5")) {
+ minimumSdVersion = game.modules.get("MaterialDeck").data.flags.minimumSDversion.replace('v','');
+ minimumMsVersion = game.modules.get("MaterialDeck").data.flags.minimumMSversion;
+ }
+ else {
+ minimumSdVersion = game.modules.get("MaterialDeck").data.minimumSDversion.replace('v','');
+ minimumMsVersion = game.modules.get("MaterialDeck").data.minimumMSversion;
+ }
+
return {
- minimumSdVersion: game.modules.get("MaterialDeck").data.minimumSDversion.replace('v',''),
+ minimumSdVersion,
localSdVersion: this.localSDversion,
masterSdVersion: this.masterSDversion,
sdDlDisable: this.masterSDversion == undefined,
- minimumMsVersion: game.modules.get("MaterialDeck").data.minimumMSversion.replace('v',''),
+ minimumMsVersion,
localMsVersion: this.localMSversion,
masterMsVersion: this.masterMSversion,
msDlDisable: this.masterMSversion == undefined,
diff --git a/src/othercontrols.js b/src/othercontrols.js
index 169dce4..0bcf063 100644
--- a/src/othercontrols.js
+++ b/src/othercontrols.js
@@ -54,6 +54,8 @@ export class OtherControls{
this.updateChatMessage(settings,context,device,options);
else if (mode == 'rollOptions')
this.updateRollOptions(settings,context,device,options);
+ else if (mode == 'rollMode')
+ this.updateRollMode(settings,context,device,options);
}
keyPress(settings,context,device){
@@ -83,6 +85,8 @@ export class OtherControls{
this.keyPressChatMessage(settings);
else if (mode == 'rollOptions')
this.keyPressRollOptions(settings);
+ else if (mode == 'rollMode')
+ this.keyPressRollMode(settings);
}
//////////////////////////////////////////////////////////////////////////////////////////////////
@@ -812,4 +816,23 @@ export class OtherControls{
this.updateAll();
}
}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+
+ updateRollMode(settings,context,device,options={}){
+ const background = settings.background ? settings.background : '#000000';
+ const ringOffColor = settings.offRing ? settings.offRing : '#000000';
+ const ringOnColor = settings.onRing ? settings.onRing : '#00FF00';
+ const iconSrc = "modules/MaterialDeck/img/other/d20.png";
+ const rollMode = settings.rollMode ? settings.rollMode : 'roll';
+ const ringColor = (rollMode == game.settings.get('core','rollMode')) ? ringOnColor : ringOffColor;
+ streamDeck.setTitle("",context);
+ streamDeck.setIcon(context,device,iconSrc,{background:background,ring:2,ringColor:ringColor,overlay:true});
+ }
+
+ async keyPressRollMode(settings){
+ const rollMode = settings.rollMode ? settings.rollMode : 'roll';
+ await game.settings.set('core','rollMode',rollMode);
+ this.updateAll();
+ }
}
\ No newline at end of file
diff --git a/src/streamDeck.js b/src/streamDeck.js
index 8f1f245..1bc6f48 100644
--- a/src/streamDeck.js
+++ b/src/streamDeck.js
@@ -452,11 +452,16 @@ export class StreamDeck{
}
ctx.drawImage(img, xStart+margin, yStart+margin, renderableWidth - 2*margin, renderableHeight - 2*margin);
if (uses != undefined && uses.heart == undefined) {
+
let txt = '';
+ let noMaxUses = false;
if (uses.available != undefined) {
txt = uses.available;
if (uses.maximum != undefined) txt = uses.available + '/' + uses.maximum;
- if (uses.maximum == undefined ) uses.maximum = 1;
+ if (uses.maximum == undefined ) {
+ uses.maximum = 1;
+ noMaxUses = true;
+ }
}
ctx.beginPath();
ctx.lineWidth = 4;
@@ -466,7 +471,8 @@ export class StreamDeck{
if (green.length == 1) green = "0"+green;
red = red.toString(16);
if (red.length == 1) red = "0"+red;
- if (uses.available == 0) ctx.strokeStyle = "#c80000";
+ if (noMaxUses) ctx.strokeStyle = "#c000000";
+ else if (uses.available == 0) ctx.strokeStyle = "#c80000";
else ctx.strokeStyle = "#"+red.toString(16)+green.toString(16)+"00";
const rect = {height:35, paddingSides:20, paddingBottom: 4}
ctx.rect(rect.paddingSides, 144-rect.height-rect.paddingBottom,144-2*rect.paddingSides,rect.height);
diff --git a/src/systems/pf2e.js b/src/systems/pf2e.js
index 21ee59f..d6a5206 100644
--- a/src/systems/pf2e.js
+++ b/src/systems/pf2e.js
@@ -1,4 +1,5 @@
import {compatibleCore} from "../misc.js";
+import {otherControls} from "../../MaterialDeck.js";
export class pf2e{
constructor(){
@@ -102,6 +103,27 @@ export class pf2e{
return this.getCondition(token,condition) != undefined;
}
+ getValuedCondition(token,condition) {
+ const effect = this.getCondition(token, condition);
+ if (effect != undefined && effect?.data.value != null) return effect;
+ }
+
+ async modifyValuedCondition(token,condition,delta) {
+ const effect = this.getValuedCondition(token,condition);
+ if (effect == undefined) {
+ if (delta > 0) {
+ const Condition = condition.charAt(0).toUpperCase() + condition.slice(1);
+ const newCondition = game.pf2e.ConditionManager.getCondition(Condition);
+ // newCondition.data.sources.hud = !0,
+ await game.pf2e.ConditionManager.addConditionToToken(newCondition, token);
+ }
+ } else {
+ const currentValue = effect.value;
+ console.log(`Current Value: ${currentValue}`);
+ await game.pf2e.ConditionManager.updateConditionValue(effect, token, currentValue+delta);
+ }
+ }
+
async toggleCondition(token,condition) {
if (condition == undefined) condition = 'removeAll';
if (condition == 'removeAll'){
@@ -113,7 +135,7 @@ export class pf2e{
if (effect == undefined) {
const Condition = condition.charAt(0).toUpperCase() + condition.slice(1);
const newCondition = game.pf2e.ConditionManager.getCondition(Condition);
- newCondition.data.sources.hud = !0,
+ // newCondition.data.sources.hud = !0,
await game.pf2e.ConditionManager.addConditionToToken(newCondition, token);
}
else {
@@ -162,6 +184,7 @@ export class pf2e{
*/
getFeatures(token,featureType) {
if (featureType == undefined) featureType = 'any';
+ if (featureType == 'action') return this.getActions(token);
const allItems = token.actor.items;
if (featureType == 'any') return allItems.filter(i => i.type == 'class' || i.type == 'feat')
else return allItems.filter(i => i.type == featureType)
@@ -193,7 +216,20 @@ export class pf2e{
}
}
+ /**
+ * Actions
+ */
+ getActions(token) {
+ const allActions = token.actor.data.data.actions;
+ return allActions.filter(a=>a.type==='strike');
+ }
+
rollItem(item) {
+ let variant = 0;
+ if (otherControls.rollOption == 'map1') variant = 1;
+ if (otherControls.rollOption == 'map2') variant = 2;
+ if(item.type==='strike') return item.variants[variant].roll({event});
+ if(item.type==='weapon') return item.parent.data.data.actions.find(a=>a.name===item.name).variants[variant].roll({event});
return item.roll()
}
}
\ No newline at end of file
diff --git a/src/systems/tokenHelper.js b/src/systems/tokenHelper.js
index aad53eb..d5df94b 100644
--- a/src/systems/tokenHelper.js
+++ b/src/systems/tokenHelper.js
@@ -221,6 +221,11 @@ export class TokenHelper{
return this.system.getResilience(token)
}
+ // /* PF2E */
+ // getStrikes(token) {
+ // return this.system.getStrikes(token);
+ // }
+
/**
* Conditions
*/
@@ -236,6 +241,16 @@ export class TokenHelper{
return this.system.toggleCondition(token,condition);
}
+ /* PF2E */
+ getValuedCondition(token,condition) {
+ return this.system.getValuedCondition(token,condition);
+ }
+
+ /* PF2E */
+ modifyValuedCondition(token,condition,delta) {
+ return this.system.modifyValuedCondition(token,condition,delta);
+ }
+
/**
* Roll
*/
diff --git a/src/token.js b/src/token.js
index b89cc50..b97564a 100644
--- a/src/token.js
+++ b/src/token.js
@@ -210,6 +210,21 @@ export class TokenControl{
}
}
}
+ else if (settings.onClick == 'valuedCondition') { //modify valued condition
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
+ streamDeck.noPermission(context,device);
+ return;
+ }
+ ring = 1;
+ overlay = true;
+ if (icon == 'stats') {
+ iconSrc = tokenHelper.getConditionIcon(settings.valuedCondition);
+ if (tokenHelper.getValuedCondition(token,settings.valuedCondition)) {
+ ring = 2;
+ ringColor = "#FF7B00";
+ }
+ }
+ }
else if (settings.onClick == 'cubCondition') { //Combat Utility Belt conditions
if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
streamDeck.noPermission(context,device);
@@ -344,6 +359,15 @@ export class TokenControl{
overlay = true;
if (icon == 'stats') iconSrc = tokenHelper.getConditionIcon(settings.condition);
}
+ else if (settings.onClick == 'valuedCondition') { //modify condition value
+ if (MODULE.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 (MODULE.getPermission('TOKEN','CONDITIONS') == false ) {
streamDeck.noPermission(context,device);
@@ -507,6 +531,11 @@ export class TokenControl{
await tokenHelper.toggleCondition(token,settings.condition);
this.update(tokenId);
}
+ else if (onClick == 'valuedCondition') { //Modify Valued condition
+ if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) return;
+ await tokenHelper.modifyValuedCondition(token,settings.valuedCondition,settings.valuedConditionDelta);
+ this.update(tokenId);
+ }
else if (onClick == 'cubCondition') { //Combat Utility Belt conditions
if (MODULE.getPermission('TOKEN','CONDITIONS') == false ) return;
const condition = settings.cubConditionName;