From edf35ab19a3f3afeb9f25230bcb76b0ad47b5f0e Mon Sep 17 00:00:00 2001 From: Lupestro Date: Sun, 31 Oct 2021 15:52:14 -0400 Subject: [PATCH] Added fixes for FoundryVTT 9 and cleaned up testing --- test/TESTCASES.md | 11 +-- test/env/TESTENV.md | 2 +- test/env/vttstart.bat | 1 + test/env/vttstart.sh | 1 + test/generic-basic-tests.js | 4 +- test/test-cases.js | 115 +++++++++++++++++----- test/vttlink.sh | 7 +- torch.js | 184 +++++++++++++++++++++++------------- 8 files changed, 224 insertions(+), 101 deletions(-) diff --git a/test/TESTCASES.md b/test/TESTCASES.md index 02e624a..485d26a 100644 --- a/test/TESTCASES.md +++ b/test/TESTCASES.md @@ -61,7 +61,7 @@ ### Token with No Torches, No Cantrips -* Player aattempts to activate torch on Token +* Player attempts to activate torch on Token * When: - [ ] Light Radius settings are default (20 units/ 40 units, 0 units/0 units) - [ ] Player Torches setting are checked @@ -69,15 +69,12 @@ - [ ] Token HUD is open in scene - [ ] Torch button is not activated * Expect: - - [ ] Torch button icon shows disabled status - orange slash through it - * Do: Player clicks torch button on token HUD - * Expect: - - [ ] Nothing happens + - [ ] Torch button does not appear -* GM aattempts to activate torch on Token +* GM attempts to activate torch on Token * When: - [ ] Light Radius settings are default (20 units/ 40 units, 0 units/0 units) - - [ ] Player Torches setting are checked + - [ ] Player Torches setting is checked - [ ] GM Uses Inventory is checked - [ ] Joined as GM - [ ] Token HUD is open in scene diff --git a/test/env/TESTENV.md b/test/env/TESTENV.md index 9edab8c..8b582ce 100644 --- a/test/env/TESTENV.md +++ b/test/env/TESTENV.md @@ -40,7 +40,7 @@ For each version of Foundry you'll be testing with, set it up for testing: 3. Install the systems, then the modules, and then the test worlds. * For Torch: * Systems: `DnD5e` and `Simple Worldbuilding System` - * Modules: `Translation: Spanish \[Core]` and the module under test + * Modules: `Translation: Spanish \[Core]`, `Quench`, and the module under test * Worlds: `torch-test-simple` and `torch-test-dnd5e`: * https://raw.githubusercontent.com/League-of-Foundry-Developers/torch/master/test/torch-test-simple/world.json * https://raw.githubusercontent.com/League-of-Foundry-Developers/torch/master/test/torch-test-5e/world.json diff --git a/test/env/vttstart.bat b/test/env/vttstart.bat index 5560ded..7e6975f 100644 --- a/test/env/vttstart.bat +++ b/test/env/vttstart.bat @@ -4,6 +4,7 @@ rem Set up structure for keeping data paths if not exist %~dp0Data mkdir %~dp0Data rem Determine Foundry subtree to use for this major version IF "%1"=="" ( SET "VTTNUM=8" ) ELSE ( SET "VTTNUM=%1" ) +if "%VTTNUM%"=="9" SET VTTVER=foundryvtt-0.9.226 if "%VTTNUM%"=="8" SET VTTVER=foundryvtt-0.8.8 if "%VTTNUM%"=="7" SET VTTVER=foundryvtt-0.7.10 if "%VTTVER%"=="" ECHO "Invalid Foundry major version specified" && EXIT /B -1 diff --git a/test/env/vttstart.sh b/test/env/vttstart.sh index f84e26b..effc5cc 100755 --- a/test/env/vttstart.sh +++ b/test/env/vttstart.sh @@ -6,6 +6,7 @@ fi case ${1:-8} in 7) vttver="foundryvtt-0.7.10";; 8) vttver="foundryvtt-0.8.8";; + 9) vttver="foundryvtt-0.9.226";; esac if [ -z ${vttver} ]; then echo "Invalid Foundry major version specified" diff --git a/test/generic-basic-tests.js b/test/generic-basic-tests.js index 822724e..7f70ded 100644 --- a/test/generic-basic-tests.js +++ b/test/generic-basic-tests.js @@ -12,12 +12,12 @@ export let torchGenericBasicTests = (context) => { describe('Tests Run As Gamemaster', () => { it('gamemaster controls the token light levels when playerTorches is false', async () => { - await game.settings.set('torch','playerTorches', true); + await game.settings.set('torch', 'playerTorches', false); await torchButtonToggles(assert, game, ACTOR, 20, 40); }); it('gamemaster controls the token light levels when playerTorches is true', async () => { - await game.settings.set('torch','playerTorches', true); + await game.settings.set('torch', 'playerTorches', true); await torchButtonToggles(assert, game, ACTOR, 20, 40); }); }); diff --git a/test/test-cases.js b/test/test-cases.js index 06922a1..e22f16e 100644 --- a/test/test-cases.js +++ b/test/test-cases.js @@ -1,59 +1,122 @@ import { waitUntil } from './test-utils.js'; + +let getLightRadii = (data) => { + return data.light + ? { bright: data.light.bright, dim: data.light.dim } + : { bright: data.brightLight, dim: data.dimLight }; +} +let lightRadiiForUpdate = async (data, bright, dim) => { + return data.light + ? { "light.bright": bright, "ight.dim": dim } + : { "brightLight": bright,"dimLight": dim }; +} + export let torchButtonToggles = async ( - assert, game, actor, brightOnRadius, dimOnRadius, brightOffRadius = 0, dimOffRadius = 0 + assert, game, actor, brightOnRadius, dimOnRadius, + brightOffRadius = 0, dimOffRadius = 0 ) => { - let token = game.scenes.current.tokens.find(token => token.name === actor); - game.canvas.hud.token.bind(token.object); + let token = game.scenes.current.tokens.find( + token => token.name === actor); + + // Open the token HUD for the targeted token + let tokenHUD = game.canvas.hud.token; + tokenHUD.bind(token.object); await waitUntil(() => { - return game.canvas.hud.token.element.find(`.col.left div`).length > 0; + return tokenHUD.element.find(`.col.left div`).length > 0; }, "HUD is up"); - let hudButtons = game.canvas.hud.token.element.find(`.col.left div`); - assert.isTrue(hudButtons.first().hasClass('torch'), 'First button is the Torch button'); + + // Examine the initial condition of the HUD + let hudButtons = tokenHUD.element.find(`.col.left div`); + assert.isTrue( + hudButtons.first().hasClass('torch'), + 'First button is the Torch button'); console.log("OldValue upon entry:", token.data.flags.torch?.oldValue); - hudButtons = game.canvas.hud.token.element.find(`.col.left div`); + hudButtons = tokenHUD.element.find(`.col.left div`); + assert.isFalse( + hudButtons.first().hasClass('active'), + "Torch isn't active before click"); + + // Click to turn on light hudButtons.first().find('i').click(); + let expectedFlag = `${brightOffRadius}/${dimOffRadius}`; await waitUntil(() => { - return token.data.brightLight > brightOffRadius; + return getLightRadii(token.data).bright > brightOffRadius; },"Torch is lit"); - assert.equal(token.data.brightLight, brightOnRadius, 'Bright light is the configured value for ON'); - assert.equal(token.data.dimLight, dimOnRadius, 'Dim light is the configured value for ON'); - hudButtons = game.canvas.hud.token.element.find(`.col.left div`); + await waitUntil(() => { + return token.data.flags.torch?.oldValue === expectedFlag; + }, "Torch flag is set"); + + // Check conditions after turning on light + assert.equal( + getLightRadii(token.data).bright, brightOnRadius, + 'Bright light is the configured value for ON'); + assert.equal( + getLightRadii(token.data).dim, dimOnRadius, + 'Dim light is the configured value for ON'); + hudButtons = tokenHUD.element.find(`.col.left div`); + await waitUntil(() => { + return hudButtons.first().hasClass('active'); + }, "Torch button active after first click"); + + // Click to turn off light hudButtons.first().find('i').click(); await waitUntil(() => { - return token.data.brightLight < brightOnRadius; + return getLightRadii(token.data).bright < brightOnRadius; }, "Torch is unlit"); - assert.equal(token.data.brightLight, brightOffRadius, 'Bright light is the configured value for OFF'); - assert.equal(token.data.dimLight, dimOffRadius, 'Dim light is the configured value for OFF'); await waitUntil(() => { return token.data.flags.torch?.oldValue === null; }, "Torch flag is cleared"); + + // Check conditions after turning off light + assert.equal( + getLightRadii(token.data).bright, brightOffRadius, + 'Bright light is the configured value for OFF'); + assert.equal( + getLightRadii(token.data).dim, dimOffRadius, + 'Dim light is the configured value for OFF'); + assert.isFalse( + hudButtons.first().hasClass('active'), + "Torch button inactive after second click"); } export let torchButtonAbsent = async ( assert, game, actor, brightOffRadius = 0, dimOffRadius = 0 ) => { - let token = game.scenes.current.tokens.find(token => token.name === actor); - game.canvas.hud.token.bind(token.object); + let token = game.scenes.current.tokens.find( + token => token.name === actor); + let tokenHUD = game.canvas.hud.token; + + // Open the token HUD for the targeted token + tokenHUD.bind(token.object); await waitUntil(() => { - return game.canvas.hud.token.element.find(`.col.left div`).length > 0; + return tokenHUD.element.find(`.col.left div`).length > 0; }, "HUD is up"); - let hudButtons = game.canvas.hud.token.element.find(`.col.left div`); - assert.isFalse(hudButtons.first().hasClass('torch'), 'First button is the Torch button'); - assert.equal(token.data.brightLight, brightOffRadius); - assert.equal(token.data.dimLight, dimOffRadius); + + // Verify that light button isn't on HUD + let hudButtons = tokenHUD.element.find(`.col.left div`); + assert.isFalse( + hudButtons.first().hasClass('torch'), + 'First button is the Torch button'); + + // Verify that the lights are off + assert.equal(getLightRadii(token.data).bright, brightOffRadius); + assert.equal(getLightRadii(token.data).dim, dimOffRadius); } export let cleanup = async ( assert, game, actor, brightOffRadius = 0, dimOffRadius = 0 ) => { + // Make sure playerTorches is off if (game.users.current.name === 'Gamemaster') { await game.settings.set('torch','playerTorches', true); } - let token = game.scenes.current.tokens.find(token => token.name === actor); - await token.update({ - "data.brightLight": brightOffRadius, - "data.dimLight": dimOffRadius - }); + // Make sure lights are off + let token = game.scenes.current.tokens.find( + token => token.name === actor); + await token.update( + lightRadiiForUpdate(token.data, brightOffRadius, dimOffRadius)); + + // Make sure token HUD is closed await game.canvas.hud.token.clear(); } diff --git a/test/vttlink.sh b/test/vttlink.sh index 15ef5e3..c58124d 100755 --- a/test/vttlink.sh +++ b/test/vttlink.sh @@ -14,10 +14,15 @@ do rm $targetdir/$(basename $file) ln -s $file $targetdir/$(basename $file) done +if [ ! -d $targetdir/test ]; then + mkdir $targetdir/test +fi # Replace javascript in test directory with link for file in $clonedir/test/*.js do echo test/$(basename $file) - rm $targetdir/test/$(basename $file) + if [ -f "$targetdir/test/$(basename $file)" ]; then + rm $targetdir/test/$(basename $file) + fi ln -s $file $targetdir/test/$(basename $file) done diff --git a/torch.js b/torch.js index 11a9d39..97050ac 100644 --- a/torch.js +++ b/torch.js @@ -7,13 +7,42 @@ * ---------------------------------------------------------------------------- */ -let DEBUG = false; +let DEBUG = true; let debugLog = (...args) => { if (DEBUG) { console.log (...args); } } + +const BUTTON_HTML = + `
`; +const DISABLED_ICON_HTML = + ``; +const CTRL_REF_HTML = (turnOffLights, ctrlOnClick) => { + return ` +

Torch

+
    +
  1. +

    ${turnOffLights}

    +
    ${ctrlOnClick}
    +
  2. +
+` +} + +// Breaking out light data into its own object is a FoundryVTT 9 feature change +let getLightRadii = (tokenData) => { + return { + bright: tokenData.light ? tokenData.light.bright : tokenData.brightLight, + dim: tokenData.light ? tokenData.light.dim : tokenData.dimLight + }; +} +let newLightRadii = (tokenData, bright, dim) => { + return tokenData.light + ? { "light.bright": bright, "light.dim": dim } + : { brightLight: bright, dimLight: dim }; +} class Torch { static async createDancingLights(tokenId) { @@ -88,15 +117,18 @@ class Torch { /* * Identify the type of light source we will be using. - * If not D&D5e, either a player or GM "fiat-lux". - * IF DND5e: + * Outside of D&D5e, either a player or GM can call for "fiat-lux". + * Within DND5e, it invokes: * - One of the spells if you've got it - first Dancing Lights then Light. * - Otherwise, the specified torch item if you've got it. - * - Failing all of those, a GM "fiat-lux" or none. + * - An indicator if you ran out of torches. + * - Failing all of those, a player doesn't even get a button to click. + * - However, a GM can always call for "fiat-lux". */ static getLightSourceType(actorId, itemName) { if (game.system.id !== 'dnd5e') { - let playersControlTorches = game.settings.get("torch", "playerTorches"); + let playersControlTorches = + game.settings.get("torch", "playerTorches"); return game.user.isGM ? 'GM' : playersControlTorches ? 'Player' : ''; } else { let items = Array.from(game.actors.get(actorId).data.items); @@ -120,8 +152,7 @@ class Torch { return item.name.toLowerCase() === itemName.toLowerCase(); }); let quantity = torchItem.data.data - ? torchItem.data.data.quantity - : item.data.quantity; + ? torchItem.data.data.quantity : item.data.quantity; return quantity > 0 ? itemName : '0'; } // GM can always deliver light by fiat without an item @@ -130,7 +161,7 @@ class Torch { } /* - * Track inventory for torch uses if we are using a torch as our light source. + * Track torch inventory if we are using a torch as our light source. */ static async consumeTorch(actorId) { // Protect against all conditions where we should not consume a torch @@ -156,9 +187,10 @@ class Torch { } } else { //0.7 and down if (torchItem.data.quantity > 0) { - await game.actors.get(actorId).updateOwnedItem( - {"_id": torchItem._id, "data.quantity": torchItem.data.quantity - 1} - ); + await game.actors.get(actorId).updateOwnedItem({ + "_id": torchItem._id, + "data.quantity": torchItem.data.quantity - 1 + }); } } } @@ -170,39 +202,42 @@ class Torch { static async addTorchButton(tokenHUD, hudHtml, hudData) { let tokenId = tokenHUD.object.id; - let tokenDoc = tokenHUD.object.document ? tokenHUD.object.document : tokenHUD.object; + let tokenDoc = tokenHUD.object.document + ? tokenHUD.object.document : tokenHUD.object; let tokenData = tokenDoc.data; - let itemName = game.system.id === 'dnd5e' ? game.settings.get("torch", "gmInventoryItemName") : ""; + let itemName = game.system.id === 'dnd5e' + ? game.settings.get("torch", "gmInventoryItemName") : ""; let torchDimRadius = game.settings.get("torch", "dimRadius"); let torchBrightRadius = game.settings.get("torch", "brightRadius"); + let currentRadius = getLightRadii(tokenData); - // Don't let the tokens we create for Dancing Lights have or use torches. :D - if (tokenData.name === 'Dancing Light' && - tokenData.dimLight === 10 && tokenData.brightLight === 0) { + // Don't let the tokens we create for Dancing Lights have or use torches. + if (tokenData.name === 'Dancing Light' + && currentRadius.dim === 10 && currentRadius.bright === 0 + ) { return; } let lightSource = Torch.getLightSourceType(tokenData.actorId, itemName); if (lightSource !== '') { - let tbutton = $( - `
`); + let tbutton = $(BUTTON_HTML); let allowEvent = true; let oldTorch = tokenDoc.getFlag("torch", "oldValue"); let newTorch = tokenDoc.getFlag("torch", "newValue"); let tokenTooBright = lightSource !== 'Dancing Lights' - && tokenData.brightLight > torchBrightRadius - && tokenData.dimLight > torchDimRadius; + && currentRadius.bright > torchBrightRadius + && currentRadius.dim > torchDimRadius; // Clear torch flags if light has been changed somehow. - let expectedTorch = tokenData.brightLight + '/' + tokenData.dimLight; + let expectedTorch = currentRadius.bright + '/' + currentRadius.dim; if (newTorch !== undefined && newTorch !== null && newTorch !== 'Dancing Lights' && newTorch !== expectedTorch) { await tokenDoc.setFlag("torch", "oldValue", null); await tokenDoc.setFlag("torch", "newValue", null); oldTorch = null; newTorch = null; - ui.notifications.warn( - `Torch: Resetting out-of-sync torch - current light: ${expectedTorch}, light in flag: ${newTorch}`); + console.warn( + `Torch: Resynchronizing - ${expectedTorch}, ${newTorch}`); } if (newTorch !== undefined && newTorch !== null) { @@ -211,8 +246,7 @@ class Torch { } else if ( lightSource === '0' || tokenTooBright) { - let disabledIcon = $( - ``); + let disabledIcon = $(DISABLED_ICON_HTML); tbutton.addClass("fa-stack"); tbutton.find('i').addClass('fa-stack-1x'); disabledIcon.addClass('fa-stack-1x'); @@ -235,7 +269,9 @@ class Torch { /* * Called when the torch button is clicked */ - static async clickedTorchButton(button, forceOff, tokenId, tokenDoc, lightSource) { + static async clickedTorchButton( + button, forceOff, tokenId, tokenDoc, lightSource + ) { debugLog("Torch clicked"); let torchOnDimRadius = game.settings.get("torch", "dimRadius"); let torchOnBrightRadius = game.settings.get("torch", "brightRadius"); @@ -243,43 +279,54 @@ class Torch { let torchOffBrightRadius = game.settings.get("torch", "offBrightRadius"); let oldTorch = tokenDoc.getFlag("torch", "oldValue"); let tokenData = tokenDoc.data; + let currentRadius = getLightRadii(tokenData); if (forceOff) { // Forcing light off... await tokenDoc.setFlag("torch", "oldValue", null); await tokenDoc.setFlag("torch", "newValue", null); - await Torch.sendRequest(tokenId, {"requestType": "removeDancingLights"}); + await Torch.sendRequest( + tokenId, {"requestType": "removeDancingLights"}); button.removeClass("active"); - await tokenDoc.update( - { brightLight: torchOffBrightRadius, dimLight: torchOffDimRadius }); - debugLog("Force torch off"); + await tokenDoc.update(newLightRadii( + tokenData, torchOffBrightRadius, torchOffDimRadius + )); + debugLog("Force torch off"); - } else if (oldTorch === null || oldTorch === undefined) { // Turning light on... - if (tokenData.brightLight === torchOnBrightRadius && tokenData.dimLight === torchOnDimRadius) { + // Turning light on... + } else if (oldTorch === null || oldTorch === undefined) { + if (currentRadius.bright === torchOnBrightRadius + && currentRadius.dim === torchOnDimRadius + ) { await tokenDoc.setFlag( - "torch", "oldValue", torchOffBrightRadius + '/' + torchOnDimRadius); - ui.notifications.warn(`Torch: Turning on torch already turned on?`); + "torch", "oldValue", + torchOffBrightRadius + '/' + torchOffDimRadius); + console.warn(`Torch: Turning on torch that's already turned on?`); } else { await tokenDoc.setFlag( - "torch", "oldValue", tokenData.brightLight + '/' + tokenData.dimLight); + "torch", "oldValue", + currentRadius.bright + '/' + currentRadius.dim); } if (lightSource === 'Dancing Lights') { await Torch.createDancingLights(tokenId); await tokenDoc.setFlag("torch", "newValue", 'Dancing Lights'); debugLog("Torch dance on"); } else { - let newBrightLight = Math.max(torchOnBrightRadius, tokenData.brightLight); - let newDimLight = Math.max(torchOnDimRadius, tokenData.dimLight); + let newBrightLight = + Math.max(torchOnBrightRadius, currentRadius.bright); + let newDimLight = + Math.max(torchOnDimRadius, currentRadius.dim); await tokenDoc.setFlag( "torch", "newValue", newBrightLight + '/' + newDimLight); - await tokenDoc.update({ - brightLight: newBrightLight, dimLight: newDimLight - }); + await tokenDoc.update(newLightRadii( + tokenData, newBrightLight, newDimLight + )); debugLog("Torch on"); } - // Any token light data update must happen before we call consumeTorch(), - // because the quantity change in consumeTorch() triggers the HUD to re-render, - // which triggers addTorchButton again. addTorchButton won't work right unless - // the change in light from the click is already a "done deal". + // Any token light data update must happen before we call + // consumeTorch(), because the quantity change in consumeTorch() + // triggers the HUD to re-render, which triggers addTorchButton again. + // addTorchButton won't work right unless the change in light from + // the click is already a "done deal". button.addClass("active"); await Torch.consumeTorch(tokenData.actorId); @@ -287,20 +334,22 @@ class Torch { let oldTorch = tokenDoc.getFlag("torch", "oldValue"); let newTorch = tokenDoc.getFlag("torch", "newValue"); if (newTorch === 'Dancing Lights') { - await Torch.sendRequest(tokenId, {"requestType": "removeDancingLights"}); + await Torch.sendRequest( + tokenId, {"requestType": "removeDancingLights"}); debugLog("Torch dance off"); } else { - let thereBeLight = oldTorch.split('/'); - if (oldTorch === newTorch) { // Something got lost - avoiding getting stuck - await tokenDoc.update({ - brightLight: torchOffBrightRadius, - dimLight: torchOffDimRadius - }); + // Something got lost - avoiding getting stuck + if (oldTorch === newTorch) { + await tokenDoc.update(newLightRadii( + tokenData, torchOffBrightRadius, torchOffDimRadius + )); } else { - await tokenDoc.update({ - brightLight: parseFloat(thereBeLight[0]), - dimLight: parseFloat(thereBeLight[1]) - }); + let thereBeLight = oldTorch.split('/'); + await tokenDoc.update(newLightRadii( + tokenData, + parseFloat(thereBeLight[0]), + parseFloat(thereBeLight[1]) + )); } debugLog("Torch off"); } @@ -320,7 +369,9 @@ class Torch { if (req.addressTo === undefined || req.addressTo === game.user._id) { let scene = game.scenes.get(req.sceneId); let reqToken = scene.data.tokens.find((token) => { - return token.id ? (token.id === req.tokenId) : (token._id === req.tokenId); + return token.id + ? (token.id === req.tokenId) + : (token._id === req.tokenId); }); let actorId = reqToken.actor ? reqToken.actor.id : reqToken.actorId; let dltoks=[]; @@ -329,10 +380,16 @@ class Torch { case 'removeDancingLights': scene.data.tokens.forEach(token => { let tokenData = token.data ? token.data : token; - let tokenActorId = (token.actor ? token.actor.id : token.actorId); + let tokenActorId = token.actor + ? token.actor.id : token.actorId; + let currentRadius = getLightRadii(tokenData); + // If the token is a dancing light owned by this actor - if (actorId === tokenActorId && token.name === 'Dancing Light' && - 10 === tokenData.dimLight && 0 === tokenData.brightLight) { + if (actorId === tokenActorId + && token.name === 'Dancing Light' + && 10 === currentRadius.dim + && 0 === currentRadius.bright + ) { if (scene.getEmbeddedDocument) { // 0.8 or higher dltoks.push(scene.getEmbeddedDocument("Token", token.id).id); } else { // 0.7 or lower @@ -356,12 +413,11 @@ Hooks.on('ready', () => { Torch.addTorchButton(app, html, data) }); Hooks.on('renderControlsReference', (app, html, data) => { + let turnOffLights = game.i18n.localize("torch.turnOffAllLights"); + let ctrlOnClick = game.i18n.localize("torch.holdCtrlOnClick"); html.find('div').first().append( - '

Torch

  1. '+ - game.i18n.localize("torch.turnOffAllLights")+ - '

    '+ - game.i18n.localize("torch.holdCtrlOnClick")+ - '
'); + CTRL_REF_HTML(turnOffLights, ctrlOnClick) + ); }); game.socket.on("module.torch", request => { Torch.handleSocketRequest(request);