Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update for 0.8.6 - works with 0.7.x #28

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Torch module for Foundry VTT

This module provides a HUD toggle button for turning on and off a configurable radius of bright and dim light around you. This base function works regardless of game system.

Additionally, in D&D5e only:
* The single HUD control will trigger the 'Dancing Lights' cantrip if you have it.
* Failing that, it will perforrm the 'Light' cantrip if you have that.
* Failing that, if you have torches, it consumes one, decrementing the quantity on each use.
* The button will show as disabled when you turn on the HUD if you have no torches left. (It doesn't currently disable the button while the HUD remains open, though, after you have extinguished your last remaining torch. Room for improvement.)

## History

This module was originally written by @Deuce. After several months of no activity, @lupestro eventually submitted the PR to get its features working reliably in FoundryVTT 0.8. After control transferred to the League, the PR was approved. The module is now somewhat actively maintained by @lupestro.

## Changelog

This has needed to be pieced together a bit, but here's what I've gleaned from the GIT history.

* 1.2.0 - June 10, 2021 - (Lupestro) Updated for 0.8.6, but ensured it still functions in 0.7.x.
* 1.1.4 - October 21, 2020 - (Stephen Hurd) Marked as 0.7.5 compatible.
* 1.1.3 - October 18, 2020 - (Stephen Hurd) Fix spelling.
* 1.1.2 - October 18, 2020 - (Stephen Hurd) Fix JSON syntax.
* 1.1.1 - October 18, 2020 - (Stephen Hurd) Name adjustment.
* 1.1.0 - October 18, 2020 - (Jose E Lozano) Add Spanish, (Stephen Hurd) Fix bright/dim radius of Dancing Lights.
* 1.0.9 - May 28, 2020 - (Stephen Hurd) Marked as 0.6.0 compatible.
* 1.0.8 - May 19, 2020 - (Aymeric DeMoura) Add French, Marked as 0.5.8 compatible.
* 1.0.7 - April 29, 2020 - (Stephen Hurd) Add Chinese, fix torch inventory usage.
* 1.0.6 - April 18, 2020 - (Stephen Hurd) Fix dancing lights removal.
* 1.0.5 - April 18, 2020 - (Stephen Hurd) Remove socket code for dancing lights removal.
* 1.0.4 - April 18, 2020 - (Stephen Hurd) Update to mark as 0.5.4 compatible.
* 1.0.3 - April 15, 2020 - (MtnTiger) - Updated with API changes.
* 1.0.2 - January 22, 2020 - (Stephen Hurd) Update for 0.4.4.
* 1.0.1 - November 26, 2019 - (Stephen Hurd) - Use await on all promises.
* 1.0.0 - November 25, 2019 - (Stephen Hurd) - Add support for Dancing Lights.

## License

"THE BEER-WARE LICENSE" (Revision 42): (From torch.js in this module)

<[email protected]> wrote this file. As long as you retain this notice you can do whatever you want with this stuff. If we meet some day, and you think this stuff is worth it, you can buy me a beer in return. Stephen Hurd
10 changes: 5 additions & 5 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "torch",
"title": "Torch",
"description": "Torch HUD Controls",
"version": "1.1.4",
"version": "1.2.0",
"author": "Deuce",
"languages": [
{
Expand Down Expand Up @@ -30,8 +30,8 @@
"socket": true,
"styles": [],
"packs": [],
"manifest": "https://raw.githubusercontent.com/RealDeuce/torch/master/module.json",
"download": "https://raw.githubusercontent.com/RealDeuce/torch/master/torch.zip",
"minimumCoreVersion": "0.5.4",
"compatibleCoreVersion": "0.7.5"
"manifest": "https://raw.githubusercontent.com/League-of-Foundry-Developers/torch/master/module.json",
"download": "https://raw.githubusercontent.com/League-of-Foundry-Developers/torch/master/torch.zip",
"minimumCoreVersion": "0.7.5",
"compatibleCoreVersion": "0.8.6"
}
122 changes: 77 additions & 45 deletions torch.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,40 @@ class Torch {
let hoff = tkn.w;
let c = tkn.center;
let v = game.settings.get("torch", "dancingLightVision")

await canvas.scene.createEmbeddedEntity("Token", [
{"actorData":{}, "actorId":tkn.actor._id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x - hoff, "y":c.y - voff},
{"actorData":{}, "actorId":tkn.actor._id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x, "y":c.y - voff},
{"actorData":{}, "actorId":tkn.actor._id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x - hoff, "y":c.y},
{"actorData":{}, "actorId":tkn.actor._id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x, "y":c.y}],
{"temporary":false, "renderSheet":false});
let tokens = [
{"actorData":{}, "actorId":tkn.actor.id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x - hoff, "y":c.y - voff},
{"actorData":{}, "actorId":tkn.actor.id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x, "y":c.y - voff},
{"actorData":{}, "actorId":tkn.actor.id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x - hoff, "y":c.y},
{"actorData":{}, "actorId":tkn.actor.id, "actorLink":false, "bar1":{"attribute":""}, "bar2":{"attribute":""}, "brightLight":0, "brightSight":0, "dimLight":10, "dimSight":0, "displayBars":CONST.TOKEN_DISPLAY_MODES.NONE, "displayName":CONST.TOKEN_DISPLAY_MODES.HOVER, "disposition":CONST.TOKEN_DISPOSITIONS.FRIENDLY, "flags":{}, "height":1, "hidden":false, "img":"systems/dnd5e/icons/spells/light-air-fire-1.jpg", "lightAlpha":1, "lightAngle":360, "lockRotation":false, "mirrorX":false, "name":"Dancing Light", "randomimg":false, "rotation":0, "scale":0.25, "sightAngle":360, "vision":v, "width":1, "x":c.x, "y":c.y}];

if (canvas.scene.createEmbeddedDocuments) { // 0.8
await canvas.scene.createEmbeddedDocuments("Token", tokens, {"temporary":false, "renderSheet":false});
} else {
await canvas.scene.createEmbeddedEntity("Token", tokens, {"temporary":false, "renderSheet":false});
}
}

/*
* Returns the first GM id.
*/
function firstGM() {
let i;

for (i=0; i<game.users.entities.length; i++) {
if (game.users.entities[i].data.role >= 4 && game.users.entities[i].active)
return game.users.entities[i].data._id;
if (game.users.contents) { // 0.8
for (i=0; i<game.users.contents.length; i++) {
if (game.users.contents[i].data.role >= 4 && game.users.contents[i].active)
return game.users.contents[i].data._id;
}
} else {
for (i=0; i<game.users.entities.length; i++) {
if (game.users.entities[i].data.role >= 4 && game.users.entities[i].active)
return game.users.entities[i].data._id;
}
}
ui.notifications.error("No GM available for Dancing Lights!");
}

async function sendRequest(req) {
req.sceneId = canvas.scene._id
req.sceneId = canvas.scene.id ? canvas.scene.id : canvas.scene._id;
req.tokenId = app.object.id;

if (!data.isGM) {
Expand Down Expand Up @@ -85,7 +95,8 @@ class Torch {
if (torches === null) {
var itemToCheck = game.settings.get("torch", "gmInventoryItemName");
if (item.name.toLowerCase() === itemToCheck.toLowerCase()) {
if (item.data.quantity > 0) {
let quantity = typeof item.data.quantity !== "undefined" ? item.data.quantity : item.data.data.quantity;
if (quantity > 0) {
torches = itemToCheck;
return;
}
Expand All @@ -109,17 +120,18 @@ class Torch {
*/
async function useTorch() {
let torch = -1;
let torchItem;

if (data.isGM && !game.settings.get("torch", "gmUsesInventory"))
return;
if (game.system.id !== 'dnd5e')
return;
if (data.isGM && !game.settings.get("torch", "gmUsesInventory"))
return;
let actor = game.actors.get(data.actorId);
if (actor === undefined)
return;

// First, check for the light cantrip...
actor.data.items.forEach((item, offset) => {
// First, check for the cantrips...
actor.data.items.forEach((item) => {
if (item.type === 'spell') {
if (item.name === 'Light') {
torch = -2;
Expand All @@ -130,24 +142,29 @@ class Torch {
return;
}
}
else {
else {
var itemToCheck = game.settings.get("torch", "gmInventoryItemName");
if (torch === -1 && item.name.toLowerCase() === itemToCheck.toLowerCase() && item.data.quantity > 0) {
torch = offset;
if (torch === -1 && item.name.toLowerCase() === itemToCheck.toLowerCase() &&
(item.data.data ? item.data.data.quantity : item.data.quantity) > 0) {
torchItem = item;
}
}
});
if (torch < 0)
if (!torchItem)
return;

// Now, remove a torch from inventory...
await actor.updateOwnedItem({"_id": actor.data.items[torch]._id, "data.quantity": actor.data.items[torch].data.quantity - 1});
if (torchItem.data.data) { //0.8
await torchItem.update({"data.quantity": torchItem.data.data.quantity - 1});
} else {
await actor.updateOwnedItem({"_id": torchItem._id, "data.quantity": torchItem.data.quantity - 1});
}
}

// Don't let Dancing Lights have/use torches. :D
if (data.name === 'Dancing Light' &&
data.dimLight === 20 &&
data.brightLight === 10) {
data.dimLight === 10 &&
data.brightLight === 0) {
return;
}

Expand All @@ -157,13 +174,14 @@ class Torch {
let tbutton = $(`<div class="control-icon torch"><i class="fas fa-fire"></i></div>`);
let allowEvent = true;
let ht = hasTorch();
let oldTorch = app.object.getFlag("torch", "oldValue");
let newTorch = app.object.getFlag("torch", "newValue");
let tokenFlagHolder = app.object.document ? app.object.document : app.object;
let oldTorch = tokenFlagHolder.getFlag("torch", "oldValue");
let newTorch = tokenFlagHolder.getFlag("torch", "newValue");

// Clear torch flags if light has been changed somehow.
if (newTorch !== undefined && newTorch !== null && newTorch !== 'Dancing Lights' && (newTorch !== data.brightLight + '/' + data.dimLight)) {
await app.object.setFlag("torch", "oldValue", null);
await app.object.setFlag("torch", "newValue", null);
await tokenFlagHolder.setFlag("torch", "oldValue", null);
await tokenFlagHolder.setFlag("torch", "newValue", null);
oldTorch = null;
newTorch = null;
}
Expand All @@ -190,34 +208,38 @@ class Torch {
let btn = $(ev.currentTarget.parentElement);
let dimRadius = game.settings.get("torch", "dimRadius");
let brightRadius = game.settings.get("torch", "brightRadius");
let oldTorch = app.object.getFlag("torch", "oldValue");
let newTorch = app.object.getFlag("torch", "newValue");
let oldTorch = tokenFlagHolder.getFlag("torch", "oldValue");
let newTorch = tokenFlagHolder.getFlag("torch", "newValue");

ev.preventDefault();
ev.stopPropagation();
if (ev.ctrlKey) { // Forcing light off...
data.brightLight = game.settings.get("torch", "offBrightRadius");
data.dimLight = game.settings.get("torch", "offDimRadius");
await app.object.setFlag("torch", "oldValue", null);
await app.object.setFlag("torch", "newValue", null);
await tokenFlagHolder.setFlag("torch", "oldValue", null);
await tokenFlagHolder.setFlag("torch", "newValue", null);
await sendRequest({"requestType": "removeDancingLights"});
btn.removeClass("active");
}
else if (oldTorch === null || oldTorch === undefined) { // Turning light on...
await app.object.setFlag("torch", "oldValue", data.brightLight + '/' + data.dimLight);
await tokenFlagHolder.setFlag("torch", "oldValue", data.brightLight + '/' + data.dimLight);
if (ht === 'Dancing Lights') {
await createDancingLights();
await app.object.setFlag("torch", "newValue", 'Dancing Lights');
await tokenFlagHolder.setFlag("torch", "newValue", 'Dancing Lights');
}
else {
if (brightRadius > data.brightLight)
data.brightLight = brightRadius;
if (dimRadius > data.dimLight)
data.dimLight = dimRadius;
await app.object.setFlag("torch", "newValue", data.brightLight + '/' + data.dimLight);
await tokenFlagHolder.setFlag("torch", "newValue", data.brightLight + '/' + data.dimLight);
}
btn.addClass("active");
useTorch();
// The token light data update must happen before we call useTorch().
// Updating the quantity on the token's embedded torch item, which happens inside useTorch(), triggers a HUD refresh.
// If the token light data isn't updated before that happens, the fresh HUD won't reflect the torch state we just changed.
await tokenFlagHolder.update({brightLight: data.brightLight, dimLight: data.dimLight});
await useTorch();
}
else { // Turning light off...
if (newTorch === 'Dancing Lights') {
Expand All @@ -228,11 +250,12 @@ class Torch {
data.brightLight = parseFloat(thereBeLight[0]);
data.dimLight = parseFloat(thereBeLight[1]);
}
await app.object.setFlag("torch", "newValue", null);
await app.object.setFlag("torch", "oldValue", null);
await tokenFlagHolder.setFlag("torch", "newValue", null);
await tokenFlagHolder.setFlag("torch", "oldValue", null);
btn.removeClass("active");
await tokenFlagHolder.update({brightLight: data.brightLight, dimLight: data.dimLight});
}
await app.object.update({brightLight: data.brightLight, dimLight: data.dimLight});
console.log("Click completed");
});
}
}
Expand All @@ -241,21 +264,30 @@ class Torch {
static async handleSocketRequest(req) {
if (req.addressTo === undefined || req.addressTo === game.user._id) {
let scn = game.scenes.get(req.sceneId);
let tkn = scn.data.tokens.find(({_id}) => _id === req.tokenId);
let tkn = scn.data.tokens.find((tokx) => tokx.id ? (tokx.id === req.tokenId) : (tokx._id === req.tokenId));
let tknActorId = tkn.actor ? tkn.actor.id : tkn.actorId;
let dltoks=[];

switch(req.requestType) {
case 'removeDancingLights':
scn.data.tokens.forEach(tok => {
if (tok.actorId === tkn.actorId &&
if (tknActorId === (tok.actor ? tok.actor.id : tok.actorId) &&
tok.name === 'Dancing Light' &&
tok.dimLight === 20 &&
tok.brightLight === 10) {
10 === (tok.data ? tok.data.dimLight : tok.dimLight) &&
0 === (tok.data ? tok.data.brightLight : tok.brightLight)) {
//let dltok = canvas.tokens.get(tok._id);
dltoks.push(scn.getEmbeddedEntity("Token", tok._id)._id);
if (scn.getEmbeddedDocument) { // 0.8
dltoks.push(scn.getEmbeddedDocument("Token", tok.id).id);
} else {
dltoks.push(scn.getEmbeddedEntity("Token", tok._id)._id);
}
}
});
await scn.deleteEmbeddedEntity("Token", dltoks);
if (scn.deleteEmbeddedDocuments) { // 0.8
await scn.deleteEmbeddedDocuments("Token", dltoks);
} else {
await scn.deleteEmbeddedEntity("Token", dltoks);
}
break;
}
}
Expand Down
Binary file modified torch.zip
Binary file not shown.