diff --git a/module.json b/module.json index 9e112df..13056fa 100644 --- a/module.json +++ b/module.json @@ -8,7 +8,7 @@ "email": "dev7355608@gmail.com" } ], - "version": "1.0.1", + "version": "1.0.2", "compatibility": { "minimum": "10", "verified": "11" @@ -26,8 +26,8 @@ }, "url": "https://github.com/dev7355608/gm-vision", "manifest": "https://github.com/dev7355608/gm-vision/releases/latest/download/module.json", - "download": "https://github.com/dev7355608/gm-vision/releases/download/v1.0.1/module.zip", - "changelog": "https://github.com/dev7355608/gm-vision/releases/tag/v1.0.1", + "download": "https://github.com/dev7355608/gm-vision/releases/download/v1.0.2/module.zip", + "changelog": "https://github.com/dev7355608/gm-vision/releases/tag/v1.0.2", "bugs": "https://github.com/dev7355608/gm-vision/issues", "readme": "https://raw.githubusercontent.com/dev7355608/gm-vision/main/README.md", "license": "https://raw.githubusercontent.com/dev7355608/gm-vision/main/LICENSE" diff --git a/script.js b/script.js index 8f38717..5dd86c6 100644 --- a/script.js +++ b/script.js @@ -1,6 +1,6 @@ -let active = false; - Hooks.once("init", () => { + let active = false; + game.settings.register("gm-vision", "active", { name: "GM Vision", scope: "client", @@ -35,243 +35,243 @@ Hooks.once("init", () => { } }); - if (foundry.utils.isNewerVersion(game.version, 11)) { - Hooks.once("setup", setup); - } else { - Hooks.once("setup", () => { - if (!game.settings.get("core", "noCanvas")) { - Hooks.once("canvasInit", setup); - } - }); - } -}); + function setup() { + if (!game.user.isGM || game.settings.get("core", "noCanvas")) { + return; + } -function setup() { - if (!game.user.isGM || game.settings.get("core", "noCanvas")) { - return; - } + active = game.settings.get("gm-vision", "active"); - active = game.settings.get("gm-vision", "active"); + Hooks.on("getSceneControlButtons", controls => { + const lighting = controls.find(c => c.name === "lighting"); - Hooks.on("getSceneControlButtons", controls => { - const lighting = controls.find(c => c.name === "lighting"); + if (!lighting) { + return; + } - if (!lighting) { - return; - } + lighting.icon = active ? "fa-solid fa-lightbulb" : "fa-regular fa-lightbulb"; + }); - lighting.icon = active ? "fa-solid fa-lightbulb" : "fa-regular fa-lightbulb"; - }); + Hooks.on("drawCanvasVisibility", layer => { + layer.gmVision = layer.addChild( + new PIXI.LegacyGraphics() + .beginFill(0xFFFFFF) + .drawShape(canvas.dimensions.rect.clone()) + .endFill()); + layer.gmVision.visible = false; + }); - Hooks.on("drawCanvasVisibility", layer => { - layer.gmVision = layer.addChild( - new PIXI.LegacyGraphics() - .beginFill(0xFFFFFF) - .drawShape(canvas.dimensions.rect.clone()) - .endFill()); - layer.gmVision.visible = false; - }); + Hooks.on("sightRefresh", layer => { + layer.gmVision.visible = active; + canvas.effects.illumination.filter.uniforms.gmVision = active; + }); - Hooks.on("sightRefresh", layer => { - layer.gmVision.visible = active; - canvas.effects.illumination.filter.uniforms.gmVision = active; - }); + libWrapper.register( + "gm-vision", + "CanvasVisibility.prototype.restrictVisibility", + function (wrapped) { + for (const token of canvas.tokens.placeables) { + token.gmVisible = false; + } - libWrapper.register( - "gm-vision", - "CanvasVisibility.prototype.restrictVisibility", - function (wrapped) { - for (const token of canvas.tokens.placeables) { - token.gmVisible = false; - } + return wrapped(); + }, + libWrapper.WRAPPER, + { perf_mode: libWrapper.PERF_FAST } + ); + + libWrapper.register( + "gm-vision", + "CanvasVisibility.prototype.testVisibility", + function (wrapped, point, options = {}) { + const result = wrapped(point, options); + const object = options.object; + + if (!(object instanceof Token)) { + return result; + } - return wrapped(); - }, - libWrapper.WRAPPER, - { perf_mode: libWrapper.PERF_FAST } - ); - - libWrapper.register( - "gm-vision", - "CanvasVisibility.prototype.testVisibility", - function (wrapped, point, options = {}) { - const result = wrapped(point, options); - const object = options.object; - - if (!(object instanceof Token)) { - return result; - } + if (active && !result || object.document.hidden && canvas.effects.visionSources.size) { + object.gmVisible = true; + object.detectionFilter = GMVisionDetectionFilter.instance; + } - if (active && !result || object.document.hidden && canvas.effects.visionSources.size) { - object.gmVisible = true; - object.detectionFilter = GMVisionDetectionFilter.instance; + return result || active; + }, + libWrapper.WRAPPER, + { perf_mode: libWrapper.PERF_FAST } + ); + + class GMVisionDetectionFilter extends AbstractBaseFilter { + /** @type {GMVisionDetectionFilter} */ + static #instance; + + /** + * The instance of this shader. + * @type {GMVisionDetectionFilter} + */ + static get instance() { + return this.#instance ??= this.create(); } - return result || active; - }, - libWrapper.WRAPPER, - { perf_mode: libWrapper.PERF_FAST } - ); - - class GMVisionDetectionFilter extends AbstractBaseFilter { - /** @type {GMVisionDetectionFilter} */ - static #instance; - - /** - * The instance of this shader. - * @type {GMVisionDetectionFilter} - */ - static get instance() { - return this.#instance ??= this.create(); - } + /** @override */ + static defaultUniforms = { + alphaScale: 1, + alphaThreshold: 0.6, + outlineColor: [1, 1, 1, 1], + thickness: 1 + }; - /** @override */ - static defaultUniforms = { - alphaScale: 1, - alphaThreshold: 0.6, - outlineColor: [1, 1, 1, 1], - thickness: 1 - }; + /** @override */ + static vertexShader = `\ + attribute vec2 aVertexPosition; - /** @override */ - static vertexShader = `\ - attribute vec2 aVertexPosition; + uniform vec4 inputSize; + uniform vec4 outputFrame; + uniform mat3 projectionMatrix; - uniform vec4 inputSize; - uniform vec4 outputFrame; - uniform mat3 projectionMatrix; + varying vec2 vTextureCoord; - varying vec2 vTextureCoord; + void main() { + vTextureCoord = aVertexPosition * (outputFrame.zw * inputSize.zw); + vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; + gl_Position = vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + }`; - void main() { - vTextureCoord = aVertexPosition * (outputFrame.zw * inputSize.zw); - vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy; - gl_Position = vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); - }`; + /** @override */ + static get fragmentShader() { + return `\ + varying vec2 vTextureCoord; + + uniform sampler2D uSampler; + uniform vec4 inputPixel; + uniform vec4 inputClamp; + uniform vec4 outlineColor; + uniform float thickness; + uniform float alphaScale; + uniform float alphaThreshold; + + float sampleAlpha(vec2 textureCoord) { + return smoothstep(alphaThreshold, 1.0, alphaScale * texture2D(uSampler, clamp(textureCoord, inputClamp.xy, inputClamp.zw)).a); + } - /** @override */ - static get fragmentShader() { - return `\ - varying vec2 vTextureCoord; + void main(void) { + float innerAlpha = sampleAlpha(vTextureCoord); + float outerAlpha = innerAlpha; - uniform sampler2D uSampler; - uniform vec4 inputPixel; - uniform vec4 inputClamp; - uniform vec4 outlineColor; - uniform float thickness; - uniform float alphaScale; - uniform float alphaThreshold; + for (float angle = 0.0; angle < ${(2 * Math.PI - this.#quality / 2).toFixed(7)}; angle += ${this.#quality.toFixed(7)}) { + vec2 offset = inputPixel.zw * vec2(cos(angle), sin(angle)) * thickness; + outerAlpha = max(outerAlpha, sampleAlpha(vTextureCoord + offset)); + } - float sampleAlpha(vec2 textureCoord) { - return smoothstep(alphaThreshold, 1.0, alphaScale * texture2D(uSampler, clamp(textureCoord, inputClamp.xy, inputClamp.zw)).a); - } + vec2 pixelCoord = vTextureCoord * inputPixel.xy; + float hatchAlpha = thickness > 1.0 ? smoothstep(0.0, 1.0, sin(2.2214415 / thickness * (pixelCoord.x + pixelCoord.y)) + 0.5) : 0.5; - void main(void) { - float innerAlpha = sampleAlpha(vTextureCoord); - float outerAlpha = innerAlpha; + gl_FragColor = outlineColor * (max((1.0 - innerAlpha) * outerAlpha, innerAlpha * hatchAlpha * 0.5) * 0.5); + }`; + } - for (float angle = 0.0; angle < ${(2 * Math.PI - this.#quality / 2).toFixed(7)}; angle += ${this.#quality.toFixed(7)}) { - vec2 offset = inputPixel.zw * vec2(cos(angle), sin(angle)) * thickness; - outerAlpha = max(outerAlpha, sampleAlpha(vTextureCoord + offset)); - } + /** + * Quality of the outline according to performance mode. + * @returns {number} + */ + static get #quality() { + switch (canvas.performance.mode) { + case CONST.CANVAS_PERFORMANCE_MODES.LOW: + return (Math.PI * 2) / 8; + case CONST.CANVAS_PERFORMANCE_MODES.MED: + return (Math.PI * 2) / 12; + default: + return (Math.PI * 2) / 16; + } + } - vec2 pixelCoord = vTextureCoord * inputPixel.xy; - float hatchAlpha = thickness > 1.0 ? smoothstep(0.0, 1.0, sin(2.2214415 / thickness * (pixelCoord.x + pixelCoord.y)) + 0.5) : 0.5; + /** @override */ + static create(uniforms) { + const shader = super.create(uniforms); - gl_FragColor = outlineColor * (max((1.0 - innerAlpha) * outerAlpha, innerAlpha * hatchAlpha * 0.5) * 0.5); - }`; - } + shader.#updatePadding(); - /** - * Quality of the outline according to performance mode. - * @returns {number} - */ - static get #quality() { - switch (canvas.performance.mode) { - case CONST.CANVAS_PERFORMANCE_MODES.LOW: - return (Math.PI * 2) / 8; - case CONST.CANVAS_PERFORMANCE_MODES.MED: - return (Math.PI * 2) / 12; - default: - return (Math.PI * 2) / 16; + return shader; } - } - - /** @override */ - static create(uniforms) { - const shader = super.create(uniforms); - shader.#updatePadding(); + #updatePadding() { + this.padding = this.uniforms.thickness; + } - return shader; - } + /** + * The thickness of the outline. + * @returns {number} + */ + get thickness() { + return this.uniforms.thickness; + } - #updatePadding() { - this.padding = this.uniforms.thickness; - } + set thickness(value) { + this.uniforms.thickness = value; + this.#updatePadding(); + } - /** - * The thickness of the outline. - * @returns {number} - */ - get thickness() { - return this.uniforms.thickness; - } + /** @override */ + get autoFit() { + return this.uniforms.thickness <= 1; + } - set thickness(value) { - this.uniforms.thickness = value; - this.#updatePadding(); - } + set autoFit(value) { } - /** @override */ - get autoFit() { - return this.uniforms.thickness <= 1; + /** @override */ + apply(filterManager, input, output, clear, currentState) { + this.uniforms.alphaScale = 1 / (currentState.target.worldAlpha || 1); + filterManager.applyFilter(this, input, output, clear); + } } - set autoFit(value) { } + Hooks.on("canvasPan", (canvas, constrained) => { + GMVisionDetectionFilter.instance.thickness = Math.max(2 * Math.abs(constrained.scale), 1); + }); - /** @override */ - apply(filterManager, input, output, clear, currentState) { - this.uniforms.alphaScale = 1 / (currentState.target.worldAlpha || 1); - filterManager.applyFilter(this, input, output, clear); - } - } + VisualEffectsMaskingFilter.defaultUniforms.gmVision = false; + VisualEffectsMaskingFilter.POST_PROCESS_TECHNIQUES.GM_VISION = { + id: "GM_VISION", + glsl: `if (gmVision) finalColor.rgb = sqrt(finalColor.rgb) * 0.5 + 0.5;` + }; - Hooks.on("canvasPan", (canvas, constrained) => { - GMVisionDetectionFilter.instance.thickness = Math.max(2 * Math.abs(constrained.scale), 1); - }); + libWrapper.register( + "gm-vision", + "VisualEffectsMaskingFilter.fragmentHeader", + function (wrapped, filterMode) { + let header = wrapped(filterMode); - VisualEffectsMaskingFilter.defaultUniforms.gmVision = false; - VisualEffectsMaskingFilter.POST_PROCESS_TECHNIQUES.GM_VISION = { - id: "GM_VISION", - glsl: `if (gmVision) finalColor.rgb = sqrt(finalColor.rgb) * 0.5 + 0.5;` - }; + if (filterMode === VisualEffectsMaskingFilter.FILTER_MODES.ILLUMINATION) { + header += "\nuniform bool gmVision;\n"; + } - libWrapper.register( - "gm-vision", - "VisualEffectsMaskingFilter.fragmentHeader", - function (wrapped, filterMode) { - let header = wrapped(filterMode); + return header; + }, + libWrapper.WRAPPER + ); + + libWrapper.register( + "gm-vision", + "VisualEffectsMaskingFilter.fragmentShader", + function (wrapped, filterMode, postProcessModes = []) { + if (filterMode === VisualEffectsMaskingFilter.FILTER_MODES.ILLUMINATION) { + postProcessModes = [...postProcessModes, "GM_VISION"]; + } - if (filterMode === VisualEffectsMaskingFilter.FILTER_MODES.ILLUMINATION) { - header += "\nuniform bool gmVision;\n"; - } + return wrapped(filterMode, postProcessModes); + }, + libWrapper.WRAPPER + ); + }; - return header; - }, - libWrapper.WRAPPER - ); - - libWrapper.register( - "gm-vision", - "VisualEffectsMaskingFilter.fragmentShader", - function (wrapped, filterMode, postProcessModes = []) { - if (filterMode === VisualEffectsMaskingFilter.FILTER_MODES.ILLUMINATION) { - postProcessModes = [...postProcessModes, "GM_VISION"]; + if (foundry.utils.isNewerVersion(game.version, 11)) { + Hooks.once("setup", setup); + } else { + Hooks.once("setup", () => { + if (!game.settings.get("core", "noCanvas")) { + Hooks.once("canvasInit", setup); } - - return wrapped(filterMode, postProcessModes); - }, - libWrapper.WRAPPER - ); -}; + }); + } +});