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

Commit

Permalink
Add setting to link actor senses to vision/detection modes (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
stwlam authored Oct 10, 2022
1 parent ea070cf commit 65e8d0d
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 11 deletions.
1 change: 1 addition & 0 deletions .dir-locals.el
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
((js2-mode . ((eval . (prettier-mode t)))))
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
1 change: 1 addition & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { printWidth: 120, tabWidth: 2 };
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
# Adequate Vision

A serviceable darkvision mode for dnd5e
Serviceable vision management for dnd5e

## The Module

This module does two things:

1. Replaces the core darkvision mode with one that has fewer unwelcome visual surprises and is more in tune with dnd5e rules.

2. Includes a setting to link each PC token's vision and detection modes with its corresponding actor's senses. This is currently in testing, but it makes no server updates. I suggest this be used with a global illumination threshold at a point where you deem "darkness" to end.

Try it, and if you don't like it, disable the setting to undo all the changes made. Send feedback if you have suggestions on how it should work.

## Usage Instructions

1. Enable module.

2. Set a token to have darkvision.
2. Set a token to have darkvision or enable "Link Actor Senses" setting.

2a. If already set prior to enabling module, set to something else and back to darkvision. This will reset the defaults.
2a. If the setting is disabled and token darvision is already set prior to enabling this module, set each applicable token's vision mode to something else and back to darkvision. This will reset the defaults.

---

Expand Down
6 changes: 3 additions & 3 deletions module.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "adequate-vision",
"title": "Adequate Vision",
"description": "A serviceable darkvision mode for dnd5e",
"description": "Serviceable vision management for dnd5e",
"authors": [
{
"name": "Shark that walks like a man"
Expand All @@ -10,10 +10,10 @@
"scripts": [
"script.js"
],
"version": "0.0.1",
"version": "0.0.2",
"compatibility": {
"minimum": "10.286",
"verified": "10.286",
"verified": "10.287",
"maximum": "10"
},
"relationships": {
Expand Down
39 changes: 39 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "adequate-vision",
"version": "0.0.2",
"description": "Serviceable vision management for dnd5e",
"main": "script.js",
"repository": {
"type": "git",
"url": "git+https://github.com/stwlam/adequate-vision.git"
},
"author": "Shark that walks like a man",
"license": "MIT",
"bugs": {
"url": "https://github.com/stwlam/adequate-vision/issues"
},
"homepage": "https://github.com/stwlam/adequate-vision#readme",
"private": true,
"devDependencies": {
"prettier": "^2.7.1"
}
}
101 changes: 96 additions & 5 deletions script.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Hooks.on("init", () => {
// Replace core vision mode
Hooks.once("init", () => {
CONFIG.Canvas.visionModes.darkvision = new VisionMode({
id: "darkvision",
label: "VISION.ModeDarkvision",
Expand All @@ -7,14 +8,104 @@ Hooks.on("init", () => {
uniforms: { enable: true, contrast: 0, saturation: -1.0, brightness: 0 },
},
lighting: {
levels: {
// [VisionMode.LIGHTING_LEVELS.DIM]: VisionMode.LIGHTING_LEVELS.BRIGHT,
},
background: { visibility: VisionMode.LIGHTING_VISIBILITY.REQUIRED },
},
vision: {
darkness: { adaptive: true },
defaults: { attenuation: 0, contrast: 0, saturation: -1.0, brightness: 0.75, range: 60 },
defaults: { contrast: 0.05, saturation: -1.0, brightness: 0.75, range: 60 },
},
});
});

// Register setting
Hooks.once("setup", () => {
game.settings.register("adequate-vision", "linkActorSenses", {
name: "Link Actor Senses (In Testing!)",
hint: "Automatically add and remove vision/detection modes according to the senses possessed by each token's corresponding actor. Currently only supported for PCs.",
scope: "world",
config: true,
default: false,
requiresReload: true,
type: Boolean,
});
});

// Update token sources every time a scene is viewed, including on initial load
Hooks.on("canvasReady", () => {
const tokens = canvas.scene?.tokens.contents ?? [];
const actors = new Set(tokens.flatMap((t) => t.actor ?? []));
for (const actor of actors) {
updateTokens(actor);
}
});

// Update token sources when an actor's senses are updated
Hooks.on("updateActor", (actor, changes, context, userId) => {
const hasSensesUpdate = Object.keys(flattenObject(changes)).some((c) => c.startsWith("system.attributes.senses"));
if (hasSensesUpdate) {
updateTokens(actor);
}
});

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

const changesKeys = Object.keys(flattenObject(changes));
if (changesKeys.some((k) => k.startsWith("sight") || k.startsWith("detectionModes"))) {
updateTokens(token.actor);
}
});

function updateTokens(actor) {
// Only make updates if the following are true
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"];
if (!checks.every((c) => c)) return;

const handledSenses = ["darkvision", "blindsight", "tremorsense"];
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 }), {});

let madeUpdates = false;
const tokens = actor.getActiveTokens(false, true).filter((t) => t.sight.enabled);
for (const token of tokens) {
const updates = {};
const { sight, detectionModes } = token._source;

// Darkvision
if (modes.darkvision && (sight.visionMode !== "darkvision" || sight.range !== modes.darkvision)) {
const defaults = CONFIG.Canvas.visionModes.darkvision.vision.defaults;
updates.sight = { visionMode: "darkvision", ...defaults, range: modes.darkvision };
} else if (sight.visionMode !== "basic") {
updates.sight = { visionMode: "basic", contrast: 0, brightness: 0, saturation: 0, range: null };
}

// Tremorsense
if (modes.tremorsense) {
const hasFeelTremor = detectionModes.some((m) => m.id === "feelTremor" && m.range === mode.tremorsense);
if (!hasFeelTremor) {
updates.detectionModes = [
{ id: "feelTremor", enabled: true, range: modes.tremorsense },
...detectionModes.filter((m) => m.id !== "feelTremor"),
];
}
} else if (detectionModes.some((m) => m.id === "feelTremor")) {
updates.detectionModes = detectionModes.filter((m) => m.id !== "feelTremor");
}

// Update?
if (Object.keys(updates).length > 0) {
token.updateSource(updates);
madeUpdates = true;
}
}

// Reinitialize vision and refresh lighting
if (madeUpdates && (game.user.character || canvas.tokens.controlled.length > 0)) {
canvas.perception.update({ initializeVision: true, refreshLighting: true }, true);
}
}

0 comments on commit 65e8d0d

Please sign in to comment.