Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

Commit

Permalink
Extend actor-sense linking to NPCs, handle AE updates (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
stwlam authored Oct 22, 2022
1 parent dd05959 commit 2bbd46a
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Version 0.1.0
* Extend actor-sense link support to NPCs
* Handle Active Effect updates (in addition to creates/deletes)

## Version 0.0.5
* Limit See Invisibility detection mode to areas of map that are actually seen (contributed by dev7355608)

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ Serviceable vision management for dnd5e

## How It Works

This module replaces the core darkvision mode with one that has fewer unwelcome visual surprises and is more in tune with dnd5e rules. It also adds a "devil's sight" vision mode and blindsight detection mode.
This module replaces some core vision and detection modes with ones that have fewer unwelcome visual surprises and is more in tune with dnd5e rules:
- Dim lighting outside an a token's darkvision range won't be rendered with higher brightness.
- If an invisible token stands in darkness, darkvision or a similar sense will be necessary to see it.
- Vision/detection modes have been added to support blindsight, truesight, and Devil's Sight.

Additionally, it includes a setting ("Link Actor Senses") to link each PC token's vision and detection modes with its corresponding actor's senses. For greater convenience, consider using this setting with a global illumination threshold at a point where you deem "darkness" to end (e.g., 0.75 darkness).

Expand Down
2 changes: 1 addition & 1 deletion module.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"scripts": [
"script.js"
],
"version": "0.0.5",
"version": "0.0.6",
"compatibility": {
"minimum": "10",
"verified": "10.288"
Expand Down
44 changes: 25 additions & 19 deletions script.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,31 @@ Hooks.on("updateActor", (actor, changes, context, userId) => {
}
});

// Handle addition and removal of Devil's Sight
// Handle updates of actor senses via AEs
Hooks.on("createActiveEffect", (effect) => {
// Could use a better check than a localization-unfriendly label
if (effect.parent instanceof Actor) {
updateTokens(effect.parent);
}
});

Hooks.on("updateActiveEffect", (effect) => {
if (effect.parent instanceof Actor) {
updateTokens(effect.parent);
}
});
Hooks.on("deleteActiveEffect", (effect) => {
if (effect.parent instanceof Actor) {
updateTokens(effect.parent);
}
});

// Update when a new token is added to a scene
// Process when a new token is added or updated
Hooks.on("createToken", (token, context, userId) => {
if (token.actor) {
Promise.resolve().then(() => {
updateTokens(token.actor);
});
}
});

// Update token sources when a token is updated
Hooks.on("updateToken", (token, changes, context, userId) => {
if (!token.actor) return;

Expand All @@ -113,13 +114,15 @@ function updateTokens(actor, { force = false } = {}) {
const linkActorSenses = game.settings.get("adequate-vision", "linkActorSenses");
const tokenVisionEnabled = !!canvas.scene?.tokenVision;
const userIsObserver = actor.getUserLevel(game.user) >= CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER;
const checks = [linkActorSenses, tokenVisionEnabled, userIsObserver, actor.type === "character"];
const checks = [linkActorSenses, tokenVisionEnabled, userIsObserver, ["character", "npc"].includes(actor.type)];
if (!checks.every((c) => c)) return;

const handledSenses = ["darkvision", "blindsight", "tremorsense", "truesight"];
const modes = Object.entries(actor.system.attributes.senses)
.filter(([sense, range]) => handledSenses.includes(sense) && typeof range === "number" && range > 0)
.reduce((entries, [sense, range]) => ({ ...entries, [sense]: range }), {});

// Could use a better check than a localization-unfriendly label
if (actor.effects.some((e) => e.label === "Devil's Sight" && !e.disabled && !e.isSuppressed)) {
modes.devilsSight = 120;
}
Expand Down Expand Up @@ -217,20 +220,19 @@ class InvisibilityDetectionMode extends DetectionMode {

/** @override */
static getDetectionFilter() {
return this._detectionFilter ??= GlowOverlayFilter.create({
glowColor: [0, 0.60, 0.33, 1]
});
return (this._detectionFilter ??= GlowOverlayFilter.create({ glowColor: [0, 0.6, 0.33, 1] }));
}

/** @override */
_canDetect(visionSource, target) {
const source = visionSource.object;

// Only invisible tokens can be detected; the vision source must not be blinded
return !(source instanceof Token
&& source.document.hasStatusEffect(CONFIG.specialStatusEffects.BLIND))
&& target instanceof Token
&& target.document.hasStatusEffect(CONFIG.specialStatusEffects.INVISIBLE);
return (
!(source instanceof Token && source.document.hasStatusEffect(CONFIG.specialStatusEffects.BLIND)) &&
target instanceof Token &&
target.document.hasStatusEffect(CONFIG.specialStatusEffects.INVISIBLE)
);
}

/** @override */
Expand All @@ -240,13 +242,15 @@ class InvisibilityDetectionMode extends DetectionMode {

// Temporarily remove the invisible status effect from the target (see TokenDocument#hasStatusEffect)
if (!target.actor) {
const icon = CONFIG.statusEffects.find(e => e.id === statusId)?.icon;
const icon = CONFIG.statusEffects.find((e) => e.id === statusId)?.icon;

effects = this.effects;
this.effects = this.effects.filter(e => e !== icon);
this.effects = this.effects.filter((e) => e !== icon);
} else {
effects = target.actor.effects.filter(e => !e.disabled && e.getFlag("core", "statusId") === statusId);
effects.forEach(e => e.disabled = true);
effects = target.actor.effects.filter((e) => !e.disabled && e.getFlag("core", "statusId") === statusId);
for (const effect of effects) {
effect.disabled = true;
}
}

// Test visibility without the invisible status effect
Expand All @@ -256,7 +260,9 @@ class InvisibilityDetectionMode extends DetectionMode {
if (!target.actor) {
this.effects = effects;
} else {
effects.forEach(e => e.disabled = false);
for (const effect of effects) {
effect.disabled = false;
}
}

return isVisible;
Expand Down

0 comments on commit 2bbd46a

Please sign in to comment.