|
|
- import { getWallBounds,getSceneSettings,migrateData,getLevelsBounds,getAdvancedLighting,migrateTokenHeight } from "./utils.js";
-
- const MODULE_ID = "wall-height";
-
- class WallHeightUtils{
- constructor(){
- this._advancedVision = null;
- this._currentTokenElevation = null;
- this.isLevels = game.modules.get("levels")?.active ?? false;
- this._isLevelsAutoCover = game.modules.get("levelsautocover")?.active ?? false;
- this._autoLosHeight = false;
- this._defaultTokenHeight = 6;
- }
-
- cacheSettings(){
- this._autoLosHeight = game.settings.get(MODULE_ID, 'autoLOSHeight');
- this._defaultTokenHeight = game.settings.get(MODULE_ID, 'defaultLosHeight');
- this._blockSightMovement = game.settings.get(MODULE_ID, "blockSightMovement");
- this._enableWallText = game.settings.get(MODULE_ID, "enableWallText");
- this.schedulePerceptionUpdate();
- }
-
- get tokenElevation(){
- return this._token?.document?.elevation ?? this.currentTokenElevation
- }
-
- set currentTokenElevation(elevation) {
- let update = false;
- const { advancedVision } = getSceneSettings(canvas.scene);
- if (this._currentTokenElevation !== elevation) {
- this._currentTokenElevation = elevation;
- if (advancedVision) {
- update = true;
- }
- }
- if (this._advancedVision !== !!advancedVision) {
- this._advancedVision = !!advancedVision;
- update = true;
- }
- if (update) {
- this.schedulePerceptionUpdate();
- }
- }
-
- get currentTokenElevation(){
- return this._currentTokenElevation;
- }
-
- schedulePerceptionUpdate(){
- if (!canvas.ready) return;
- canvas.perception.update({
- forceUpdateFog: true,
- initializeLighting: true,
- initializeSounds: true,
- initializeVision: true,
- refreshLighting: true,
- refreshSounds: true,
- refreshTiles: true,
- refreshVision: true,
- }, true);
- }
-
- updateCurrentTokenElevation() {
- const token = canvas.tokens.controlled.find(t => t.document.sight.enabled) ?? canvas.tokens.controlled[0];
- if (!token && game.user.isGM) {
- this.currentTokenElevation = null;
- this._token = null;
- } else if (token) {
- this.currentTokenElevation = token.losHeight
- this._token = token;
- }
- }
-
- async setSourceElevationTop(document, value) {
- if (document instanceof TokenDocument) return;
- return await document.update({ "flags.levels.rangeTop": value });
- }
-
- getSourceElevationTop(document) {
- if (document instanceof TokenDocument) return document.object.losHeight
- return document.document.flags?.levels?.rangeTop ?? +Infinity;
- }
-
- async setSourceElevationBottom(document, value) {
- if (document instanceof TokenDocument) return await document.update({ "elevation": bottom });
- return await document.update({ "flags.levels.rangeBottom": value });
- }
-
- getSourceElevationBottom(document) {
- if (document instanceof TokenDocument) return document.document.elevation;
- return document.document.flags?.levels?.rangeBottom ?? -Infinity;
- }
-
- async setSourceElevationBounds(document, bottom, top) {
- if (document instanceof TokenDocument) return await document.update({ "elevation": bottom });
- return await document.update({ "flags.levels.rangeBottom": bottom, "flags.levels.rangeTop": top });
- }
-
- getSourceElevationBounds(document) {
- if (document instanceof TokenDocument) {
- const bottom = document.document.elevation;
- const top = document.object
- ? document.object.losHeight
- : bottom;
- return { bottom, top };
- }
- return getLevelsBounds(document);
- }
-
- async setSourceElevationBounds(document, bottom, top) {
- if (document instanceof TokenDocument) return await document.update({ "elevation": bottom });
- return await document.update({ "flags.levels.rangeBottom": bottom, "flags.levels.rangeTop": top });
- }
-
- getSourceElevationBounds(document) {
- if (document instanceof TokenDocument) {
- const bottom = document.elevation;
- const top = document.object
- ? document.object.losHeight
- : bottom;
- return { bottom, top };
- }
- return getLevelsBounds(document);
- }
-
- async removeOneToWalls(scene){
- if(!scene) scene = canvas.scene;
- const walls = Array.from(scene.walls);
- const updates = [];
- for(let wall of walls){
- const oldTop = wall.document.flags?.["wall-height"]?.top;
- if(oldTop != null && oldTop != undefined){
- const newTop = oldTop - 1;
- updates.push({_id: wall.id, "flags.wall-height.top": newTop});
- }
- }
- if(updates.length <= 0) return false;
- await scene.updateEmbeddedDocuments("Wall", updates);
- ui.notifications.notify("Wall Height - Added +1 to " + updates.length + " walls in scene " + scene.name);
- return true;
- }
-
- async migrateTokenHeight(){
- return await migrateTokenHeight();
- }
-
- async migrateData(scene){
- return await migrateData(scene);
- }
-
- async migrateCompendiums (){
- let migratedScenes = 0;
- const compendiums = Array.from(game.packs).filter(p => p.documentName === 'Scene');
- for (const compendium of compendiums) {
- const scenes = await compendium.getDocuments();
- for(const scene of scenes){
- const migrated = await migrateData(scene);
- if(migrated) migratedScenes++;
- }
- }
- if(migratedScenes > 0){
- ui.notifications.notify(`Wall Height - Migrated ${migratedScenes} scenes to new Wall Height data structure.`);
- console.log(`Wall Height - Migrated ${migratedScenes} scenes to new Wall Height data structure.`);
- }else{
- ui.notifications.notify(`Wall Height - No scenes to migrate.`);
- console.log(`Wall Height - No scenes to migrate.`);
- }
- return migratedScenes;
- }
-
- async migrateScenes (){
- const scenes = Array.from(game.scenes);
- let migratedScenes = 0;
- ui.notifications.warn("Wall Height - Migrating all scenes, do not refresh the page!");
- for(const scene of scenes){
- const migrated = await migrateData(scene);
- if(migrated) migratedScenes++;
- }
- if(migratedScenes > 0){
- ui.notifications.notify(`Wall Height - Migrated ${migratedScenes} scenes to new Wall Height data structure.`);
- console.log(`Wall Height - Migrated ${migratedScenes} scenes to new Wall Height data structure.`);
- }else{
- ui.notifications.notify(`Wall Height - No scenes to migrate.`);
- console.log(`Wall Height - No scenes to migrate.`);
- }
- return migratedScenes;
- }
-
- async migrateAll(){
- ui.notifications.error(`Wall Height - WARNING: The new data structure requires Better Roofs, Levels and 3D Canvas and Token Attacher to be updated!`);
- await WallHeight.migrateScenes();
- await WallHeight.migrateCompendiums();
- ui.notifications.notify(`Wall Height - Migration Complete.`);
- await game.settings.set(MODULE_ID, 'migrateOnStartup', false);
- }
-
- async setWallBounds(bottom, top, walls){
- if(!walls) walls = canvas.walls.controlled.length ? canvas.walls.controlled : canvas.walls.placeables;
- walls instanceof Array || (walls = [walls]);
- const updates = [];
- for(let wall of walls){
- updates.push({_id: wall.id, "flags.wall-height.top": top, "flags.wall-height.bottom": bottom});
- }
- return await canvas.scene.updateEmbeddedDocuments("Wall", updates);
- }
-
- getWallBounds(wall){
- return getWallBounds(wall);
- }
-
- addBoundsToRays(rays, token) {
- if (token) {
- const bottom = token.document.elevation;
- const top = WallHeight._blockSightMovement ? token.losHeight : token.document.elevation;
- for (const ray of rays) {
- ray.A.b = bottom;
- ray.A.t = top;
- }
- }
- return rays;
- }
-
- }
-
- export function registerWrappers() {
- globalThis.WallHeight = new WallHeightUtils();
-
- if(!globalThis.WallHeight.isLevels){
- Object.defineProperty(AmbientLightDocument.prototype, "elevation", {
- get: function () {
- return this.flags?.levels?.rangeBottom ?? canvas.primary.background.elevation;
- }
- });
- }
-
-
- function tokenOnUpdate(wrapped, ...args) {
- wrapped(...args);
-
- updateTokenSourceBounds(this);
- }
-
- function updateTokenSourceBounds(token) {
- const { advancedVision } = getSceneSettings(token.scene);
- const losHeight = token.losHeight;
- const sourceId = token.sourceId;
- if (!advancedVision) {
- if (canvas.effects.visionSources.has(sourceId)) {
- token.vision.los.origin.b = token.vision.los.origin.t = losHeight;
- }
- if (canvas.effects.lightSources.has(sourceId)) {
- token.light.los.origin.b = token.light.los.origin.t = losHeight;
- }
- } else if (canvas.effects.visionSources.has(sourceId) && (token.vision.los.origin.b !== losHeight || token.vision.los.origin.t !== losHeight)
- || canvas.effects.lightSources.has(sourceId) && (token.light.los.origin.b !== losHeight || token.light.los.origin.t !== losHeight)) {
- token.updateSource({ defer: true });
- canvas.perception.update({
- forceUpdateFog: true,
- initializeLighting: true,
- initializeSounds: true,
- initializeVision: true,
- refreshLighting: true,
- refreshSounds: true,
- refreshTiles: true,
- refreshVision: true,
- }, true);
- }
- }
-
- function testWallInclusion(wrapped, ...args) {
- if (!wrapped(...args)) return false;
- const wall = args[0]
- const { advancedVision } = getSceneSettings(wall.scene);
- if (!advancedVision) return true;
- const { top, bottom } = getWallBounds(wall);
- const b = this.config?.source?.object?.b ?? this.origin?.object?.b ?? -Infinity;
- const t = this.config?.source?.object?.t ?? this.origin?.object?.t ?? +Infinity;
- return b >= bottom && t <= top;
- }
-
- function isDoorVisible(wrapped, ...args) {
- const wall = this.wall;
- const { advancedVision } = getSceneSettings(wall.scene);
- const isUI = CONFIG.Levels?.UI?.rangeEnabled && !canvas?.tokens?.controlled[0];
- const elevation = WallHeight.currentTokenElevation//isUI && !canvas.tokens.controlled[0] ? WallHeight.currentTokenElevation : WallHeight._token?.document?.elevation;
- if (elevation == null || !advancedVision) return wrapped(...args);
- const {top, bottom} = getWallBounds(wall);
- let inRange = elevation >= bottom && elevation <= top;
- if (isUI) inRange = elevation >= bottom && elevation < top;
- //if (elevation < bottom || elevation > top) return false;
- return wrapped(...args) && inRange;
- }
-
- function setSourceElevation(wrapped, origin, config = {}, ...args) {
-
- let bottom = -Infinity;
- let top = +Infinity;
- const object = config.source?.object ?? origin.object;
- if (origin.b == undefined && origin.t == undefined) {
- if (object instanceof Token) {
- if (config.type !== "move") {
- bottom = top = object.losHeight;
- } else {
- bottom = object.document.elevation;
- top = WallHeight._blockSightMovement ? object.losHeight : bottom;
- }
- } else if (object instanceof AmbientLight || object instanceof AmbientSound) {
- if (getAdvancedLighting(object.document)) {
- const bounds = getLevelsBounds(object.document)//WallHeight.getElevation(object.document);
- bottom = bounds.bottom;
- top = bounds.top;
- } else {
- bottom = WallHeight.currentTokenElevation;
- if (bottom == null) {
- bottom = -Infinity;
- top = +Infinity;
- } else {
- top = bottom;
- }
- }
- }
- }
- if(object){
- object.b = origin.b ?? bottom;
- object.t = origin.t ?? top;
- }
- return wrapped(origin, config, ...args);
- }
-
- function drawWallRange(wrapped, ...args){
- const { advancedVision } = getSceneSettings(canvas.scene);
- const bounds = getWallBounds(this);
- if(!WallHeight._enableWallText || !advancedVision || (bounds.top == Infinity && bounds.bottom == -Infinity)) {
- this.children = this.children.filter(c => c.name !== "wall-height-text");
- return wrapped(...args);
-
- }
- wrapped(...args);
- const style = CONFIG.canvasTextStyle.clone();
- style.fontSize /= 1.5;
- style.fill = this._getWallColor();
- if(bounds.top == Infinity) bounds.top = "Inf";
- if(bounds.bottom == -Infinity) bounds.bottom = "-Inf";
- const range = `${bounds.top} / ${bounds.bottom}`;
- const oldText = this.children.find(c => c.name === "wall-height-text");
- const text = oldText ?? new PreciseText(range, style);
- text.text = range;
- text.name = "wall-height-text";
- let angle = (Math.atan2( this.coords[3] - this.coords[1], this.coords[2] - this.coords[0] ) * ( 180 / Math.PI ));
- angle = (angle+90)%180 - 90;
- text.position.set(this.center.x, this.center.y);
- text.anchor.set(0.5, 0.5);
- text.angle = angle;
- if(!oldText) this.addChild(text);
- }
-
- Hooks.on("updateToken", () => {
- WallHeight.updateCurrentTokenElevation();
- });
-
- Hooks.on("controlToken", () => {
- WallHeight.updateCurrentTokenElevation();
- });
-
- Hooks.on("updateScene", (doc, change) => {
- WallHeight.updateCurrentTokenElevation();
- });
-
- Hooks.on("canvasInit", () => {
- WallHeight._advancedVision = null;
- WallHeight._currentTokenElevation = null;
- WallHeight._token = null;
- });
-
- Hooks.on("canvasReady", () => {
- WallHeight.updateCurrentTokenElevation();
- });
-
- Hooks.on("updateWall", (wall, updates) => {
- if(updates.flags && updates.flags[MODULE_ID]) {
- WallHeight.schedulePerceptionUpdate();
- }
- })
-
- libWrapper.register(MODULE_ID, "DoorControl.prototype.isVisible", isDoorVisible, "MIXED");
-
- libWrapper.register(MODULE_ID, "CONFIG.Token.objectClass.prototype._onUpdate", tokenOnUpdate, "WRAPPER");
-
- libWrapper.register(MODULE_ID, "ClockwiseSweepPolygon.prototype._testWallInclusion", testWallInclusion, "WRAPPER", { perf_mode: "FAST" });
-
- libWrapper.register(MODULE_ID, "ClockwiseSweepPolygon.prototype.initialize", setSourceElevation, "WRAPPER");
-
- libWrapper.register(MODULE_ID, "Wall.prototype.refresh", drawWallRange, "WRAPPER");
-
- libWrapper.register(
- MODULE_ID,
- "VisionSource.prototype.elevation",
- function () {
- return this.object?.losHeight ?? canvas.primary.background.elevation;
- },
- libWrapper.OVERRIDE,
- { perf_mode: libWrapper.PERF_FAST }
- );
-
- libWrapper.register(
- MODULE_ID,
- "LightSource.prototype.elevation",
- function () {
- return this.object instanceof Token
- ? this.object.losHeight
- : this.object?.document?.elevation ?? canvas.primary.background.elevation;
- },
- libWrapper.OVERRIDE,
- { perf_mode: libWrapper.PERF_FAST }
- );
- }
|