All user data for FoundryVTT. Includes worlds, systems, modules, and any asset in the "foundryuserdata" directory. Does NOT include the FoundryVTT installation itself.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

803 lines
26 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. class LevelsUI extends FormApplication {
  2. constructor() {
  3. super();
  4. this.range = [];
  5. this.rangeEnabled = false;
  6. this.isEdit = false;
  7. this.definedLevels = [];
  8. this.roofEnabled = false;
  9. this.placeOverhead = false;
  10. this.stairEnabled = true;
  11. }
  12. static get defaultOptions() {
  13. return {
  14. ...super.defaultOptions,
  15. title: game.i18n.localize("levels.ui.title"),
  16. id: "levelsUI",
  17. template: `modules/levels/templates/layerTool.hbs`,
  18. resizable: true,
  19. dragDrop: [{ dragSelector: null, dropSelector: null }],
  20. };
  21. }
  22. get currentRange() {
  23. const bgElev = canvas.primary.background.elevation;
  24. if(!this.rangeEnabled) return { bottom: bgElev, top: bgElev };
  25. if(!this.range?.length) return { bottom: bgElev, top: bgElev };
  26. return {
  27. bottom: parseFloat(this.range[0]),
  28. top: parseFloat(this.range[1]),
  29. }
  30. }
  31. getRange() {
  32. return {
  33. bottom: parseFloat(this.range[0] ?? -Infinity),
  34. top: parseFloat(this.range[1] ?? Infinity),
  35. }
  36. }
  37. getData() {
  38. return {
  39. lightMasking: canvas.scene.getFlag(CONFIG.Levels.MODULE_ID, "lightMasking") ?? true,
  40. };
  41. }
  42. async activateListeners(html) {
  43. ui.controls.control.foreground = true;
  44. canvas.tiles._activateSubLayer(true);
  45. this.rangeEnabled = true;
  46. this.loadLevels();
  47. html.on("click", ".level-item", this._onChangeLevel.bind(this));
  48. html.on("click", ".level-item .fa-trash", this._onRemoveLevel.bind(this));
  49. html.on(
  50. "click",
  51. "#levels-ui-controls .fa-trash",
  52. this._onClearLevels.bind(this)
  53. );
  54. html.on(
  55. "click",
  56. "#levels-ui-controls .fa-plus",
  57. this._onAddLevel.bind(this)
  58. );
  59. html.on(
  60. "click",
  61. "#levels-ui-controls .fa-edit",
  62. this._onToggleEdit.bind(this)
  63. );
  64. html.on(
  65. "click",
  66. "#levels-ui-controls .fa-map",
  67. this._onGetFromScene.bind(this)
  68. );
  69. html.on(
  70. "click",
  71. "#levels-ui-controls .fa-users",
  72. this._onShowPlayerList.bind(this)
  73. );
  74. html.on(
  75. "click",
  76. "#levels-ui-controls .fa-archway",
  77. () => {
  78. this.roofEnabled = !this.roofEnabled;
  79. this.setButtonStyles();
  80. this.computeLevelsVisibility();
  81. }
  82. );
  83. html.on(
  84. "click",
  85. "#levels-ui-controls .fa-tree",
  86. () => {
  87. this.placeOverhead = !this.placeOverhead;
  88. this.setButtonStyles();
  89. }
  90. );
  91. html.on(
  92. "click",
  93. "#levels-ui-controls .fa-sort-amount-up-alt",
  94. () => {
  95. this.stairEnabled = !this.stairEnabled;
  96. this.setButtonStyles();
  97. }
  98. );
  99. html.on(
  100. "click",
  101. "#levels-ui-controls .fa-circle-exclamation",
  102. async () => {
  103. await canvas.scene.setFlag(CONFIG.Levels.MODULE_ID, "lightMasking", false);
  104. this.render(true);
  105. }
  106. )
  107. html.on("drop", (event) => {
  108. let data;
  109. try {
  110. data = JSON.parse(event.originalEvent.dataTransfer.getData("text/plain"));
  111. } catch (err) {
  112. return false;
  113. }
  114. if(data.type === "Scene") this._onDropScene(data.uuid)
  115. });
  116. html.on("click", ".player-portrait", this._onControlToken.bind(this));
  117. html.on("change", ".level-inputs input", this.saveData.bind(this));
  118. //make list sortable
  119. html.find("#levels-list").sortable({
  120. axis: "y",
  121. handle: ".fa-arrows-alt",
  122. update: this.saveData.bind(this),
  123. });
  124. const index = this.definedLevels
  125. ? this.definedLevels.indexOf(
  126. this.definedLevels.find(
  127. (l) =>
  128. l[0] == this.range[0] &&
  129. l[1] == this.range[1] &&
  130. l[2] == this.range[2]
  131. )
  132. )
  133. : undefined;
  134. if (index === undefined || index === -1) {
  135. html.find("#levels-list li:last-child").click();
  136. } else html.find("#levels-list li")[index].click();
  137. this.updatePlayerList();
  138. this.setButtonStyles();
  139. }
  140. setButtonStyles() {
  141. this.element.find(".fa-archway").toggleClass("active", this.roofEnabled);
  142. this.element.find(".fa-tree").toggleClass("active", this.placeOverhead);
  143. this.element.find(".fa-sort-amount-up-alt").toggleClass("active", this.stairEnabled);
  144. this.element.find(".fa-users").toggleClass("active", this.element.find(".players-on-level").hasClass("active"));
  145. }
  146. _onAddLevel(event) {
  147. let $li = this.generateLi([0, 0, ""]);
  148. $("#levels-list").append($li);
  149. }
  150. //toggle readonly property of inputs and toggle visibility of trash icon
  151. _onToggleEdit(event) {
  152. this.isEdit = !this.isEdit;
  153. let $inputs = $(".level-inputs input");
  154. $inputs.prop("readonly", !$inputs.prop("readonly"));
  155. $(".level-item .fa-trash").toggleClass("hidden");
  156. $(".level-item .fa-arrows-alt").toggleClass("hidden");
  157. this.saveData();
  158. }
  159. activateForeground(){
  160. try{
  161. ui.controls.control.foreground = true;
  162. ui.controls.control.foreground = true;
  163. canvas.tiles._activateSubLayer(true);
  164. canvas.perception.update({refreshLighting: true, refreshTiles: true}, true);
  165. const fgControl = document.querySelector(`[data-tool="foreground"]`)
  166. if(fgControl) fgControl.classList.add("active")
  167. }catch(e){}
  168. }
  169. _onChangeLevel(event) {
  170. this.activateForeground()
  171. if (!$(event.target).hasClass("player-portrait"))
  172. canvas.tokens.releaseAll();
  173. let $target = $(event.currentTarget);
  174. let $parent = $target.parent();
  175. $parent.find(".fa-caret-right").removeClass("active");
  176. $parent.find("li").removeClass("active");
  177. $target.find(".fa-caret-right").addClass("active");
  178. $target.addClass("active");
  179. const top = $target.find(".level-top").val();
  180. const bottom = $target.find(".level-bottom").val();
  181. const name = $target.find(".level-name").val();
  182. this.definedLevels = canvas.scene.getFlag(
  183. CONFIG.Levels.MODULE_ID,
  184. "sceneLevels"
  185. );
  186. this.range = this.definedLevels?.find((l) => l[0] == bottom && l[1] == top) ?? [parseFloat(bottom), parseFloat(top)];
  187. if ($(event.target).hasClass("player-portrait")) return;
  188. WallHeight.currentTokenElevation = parseFloat(bottom);
  189. this.computeLevelsVisibility(this.range);
  190. Hooks.callAll("levelsUiChangeLevel");
  191. }
  192. _onRemoveLevel(event) {
  193. Dialog.confirm({
  194. title: game.i18n.localize("levels.dialog.removeLevel.title"),
  195. content: game.i18n.localize("levels.dialog.removeLevel.content"),
  196. yes: () => {
  197. let $target = $(event.currentTarget);
  198. $target.remove();
  199. this.saveData();
  200. },
  201. no: () => {},
  202. defaultYes: false,
  203. });
  204. }
  205. _onGetFromScene(event) {
  206. Dialog.confirm({
  207. title: game.i18n.localize("levels.dialog.getFromScene.title"),
  208. content: game.i18n.localize("levels.dialog.getFromScene.content"),
  209. yes: (html) => {
  210. const maxRange = parseFloat(html.find("#maxelevationdifference").val());
  211. this.getFromScene(maxRange);
  212. },
  213. no: () => {},
  214. render: (html) => {
  215. const maxRange = `
  216. <hr>
  217. <div class="form-group" style="display: grid;grid-template-columns: 1fr 1fr;align-items: center;">
  218. <label>${game.i18n.localize("levels.ui.minElevDiff")}</label>
  219. <input type="text" id="maxelevationdifference" data-dtype="Number" value="9" placeholder="">
  220. </div>
  221. <br>
  222. `
  223. $(html[0]).append(maxRange);
  224. },
  225. defaultYes: false,
  226. });
  227. }
  228. _onShowPlayerList(event) {
  229. this.element.find(".players-on-level").toggleClass("active");
  230. this.setButtonStyles();
  231. }
  232. _onControlToken(event) {
  233. canvas.tokens.releaseAll();
  234. const tokenId = event.currentTarget.dataset.tokenid;
  235. const token = canvas.tokens.get(tokenId);
  236. token.control();
  237. }
  238. async _onDropScene(uuid) {
  239. const scene = await fromUuid(uuid);
  240. if (!scene) return;
  241. const dialogResult = await Dialog.confirm({
  242. title: game.i18n.localize("levels.dialog.sceneDrop.title"),
  243. content: game.i18n.localize("levels.dialog.sceneDrop.content"),
  244. })
  245. if (!dialogResult) return;
  246. const collections = Object.keys(scene.collections);
  247. for (const collection of collections) {
  248. const documents = Array.from(scene[collection]);
  249. if(!documents.length) continue;
  250. await canvas.scene.createEmbeddedDocuments(documents[0].documentName, documents);
  251. }
  252. const sceneBg = scene.background.src;
  253. if(!sceneBg) return;
  254. const {sceneWidth, sceneHeight, sceneX, sceneY} = scene.dimensions;
  255. await canvas.scene.createEmbeddedDocuments("Tile", [{
  256. "texture.src": sceneBg,
  257. "width": sceneWidth,
  258. "height": sceneHeight,
  259. "x": sceneX,
  260. "y": sceneY,
  261. }]);
  262. }
  263. saveData() {
  264. let data = [];
  265. $(this.element)
  266. .find("li")
  267. .each((index, element) => {
  268. let $element = $(element);
  269. let name = $element.find(".level-name").val();
  270. let top = $element.find(".level-top").val();
  271. let bottom = $element.find(".level-bottom").val();
  272. data.push([bottom, top, name]);
  273. });
  274. canvas.scene.setFlag(CONFIG.Levels.MODULE_ID, "sceneLevels", data);
  275. }
  276. async loadLevels() {
  277. $("#levels-list").empty();
  278. let levelsFlag =
  279. canvas.scene.getFlag(CONFIG.Levels.MODULE_ID, "sceneLevels") || [];
  280. this.definedLevels = levelsFlag;
  281. this.range = this.range ?? this.definedLevels[levelsFlag.length - 1];
  282. if (levelsFlag) {
  283. for (let level of levelsFlag) {
  284. this.element.find("#levels-list").append(this.generateLi(level));
  285. }
  286. }
  287. }
  288. generateLi(data) {
  289. //data 0 - top 1- bottom 2- name
  290. let $li = $(`
  291. <li class="level-item" draggable>
  292. <i class="fas fa-arrows-alt"></i>
  293. <div class="players-on-level"></div>
  294. <i class="fas fa-caret-right"></i>
  295. <div class="level-inputs">
  296. <input type="text" class="level-name" value="${
  297. data[2] ?? ""
  298. }" placeholder="${game.i18n.localize("levels.widget.element")}">
  299. <i class="fas fa-caret-down"></i>
  300. <input type="number" class="level-bottom" value="${
  301. data[0]
  302. }" placeholder="0">
  303. <i class="fas fa-caret-up"></i>
  304. <input type="number" class="level-top" value="${data[1]}" placeholder="0">
  305. <i class="fas fa-trash"></i>
  306. </div>
  307. </li>
  308. `);
  309. $li.find("input").prop("readonly", !this.isEdit);
  310. $li.find(".fa-trash").toggleClass("hidden", this.isEdit);
  311. $li.find(".fa-arrows-alt").toggleClass("hidden", this.isEdit);
  312. return $li;
  313. }
  314. async _onClearLevels() {
  315. Dialog.confirm({
  316. title: game.i18n.localize("levels.dialog.levelsclear.title"),
  317. content: game.i18n.localize("levels.dialog.levelsclear.content"),
  318. yes: async () => {
  319. await canvas.scene.setFlag(CONFIG.Levels.MODULE_ID, "sceneLevels", []);
  320. this.loadLevels();
  321. },
  322. no: () => {},
  323. defaultYes: false,
  324. });
  325. }
  326. updatePlayerList() {
  327. const playerActors = Array.from(game.users).map(
  328. (user) => user.character?.id
  329. );
  330. const players = canvas.tokens.placeables.filter((token) =>
  331. playerActors.includes(token.actor?.id)
  332. );
  333. $(this.element)
  334. .find("li")
  335. .each((index, element) => {
  336. let $element = $(element);
  337. const top = $element.find(".level-top").val();
  338. const bottom = $element.find(".level-bottom").val();
  339. const $playerList = $element.find(".players-on-level");
  340. $playerList.empty();
  341. players.forEach((player) => {
  342. if (
  343. player.losHeight >= bottom &&
  344. player.losHeight < top &&
  345. player.id
  346. ) {
  347. const color = Array.from(game.users).find(
  348. (user) => user.character?.id == player?.actor?.id
  349. )?.border;
  350. $playerList.append(
  351. `<img class="player-portrait" data-tokenid="${player.id}" title="${player.actor?.name}" style="border-color: ${color}" src="${player.document.texture.src}">`
  352. );
  353. }
  354. });
  355. });
  356. this.element.css("height", "auto");
  357. }
  358. close(force = false) {
  359. if (!force) this.saveData();
  360. this.rangeEnabled = false;
  361. if (!force) this.computeLevelsVisibility();
  362. CONFIG.Levels.handlers.RefreshHandler.restoreVisAll();
  363. CONFIG.Levels.handlers.RefreshHandler.refreshAll();
  364. WallHeight.schedulePerceptionUpdate()
  365. super.close();
  366. }
  367. async getFromScene(maxRange = 9) {
  368. let autoLevels = {};
  369. for (let wall of canvas.walls.placeables) {
  370. const { top, bottom } = WallHeight.getWallBounds(wall);
  371. let entityRange = [bottom, top];
  372. if (
  373. entityRange[0] != -Infinity &&
  374. entityRange[1] != Infinity &&
  375. (entityRange[0] || entityRange[0] == 0) &&
  376. (entityRange[1] || entityRange[1] == 0)
  377. ) {
  378. autoLevels[`${entityRange[0]}${entityRange[1]}`] = entityRange;
  379. }
  380. }
  381. for (let tile of canvas.tiles.placeables.filter(
  382. (t) => t.document.overhead
  383. )) {
  384. let { rangeBottom, rangeTop } =
  385. CONFIG.Levels.helpers.getRangeForDocument(tile);
  386. if (
  387. (rangeBottom || rangeBottom == 0) &&
  388. (rangeTop || rangeTop == 0) &&
  389. rangeTop != Infinity &&
  390. rangeBottom != -Infinity
  391. ) {
  392. autoLevels[`${rangeBottom}${rangeTop}`] = [rangeBottom, rangeTop];
  393. }
  394. }
  395. for (let light of canvas.lighting.placeables) {
  396. let { rangeBottom, rangeTop } =
  397. CONFIG.Levels.helpers.getRangeForDocument(light);
  398. if (
  399. (rangeBottom || rangeBottom == 0) &&
  400. (rangeTop || rangeTop == 0) &&
  401. rangeTop != Infinity &&
  402. rangeBottom != -Infinity
  403. ) {
  404. autoLevels[`${rangeBottom}${rangeTop}`] = [rangeBottom, rangeTop];
  405. }
  406. }
  407. for (let drawing of canvas.drawings.placeables) {
  408. let { rangeBottom, rangeTop } =
  409. CONFIG.Levels.helpers.getRangeForDocument(drawing);
  410. if (
  411. (rangeBottom || rangeBottom == 0) &&
  412. (rangeTop || rangeTop == 0) &&
  413. rangeTop != Infinity &&
  414. rangeBottom != -Infinity
  415. ) {
  416. autoLevels[`${rangeBottom}${rangeTop}`] = [rangeBottom, rangeTop];
  417. }
  418. }
  419. let autoRange = Object.entries(autoLevels)
  420. .map((x) => x[1])
  421. .filter( x => Math.abs(x[1] - x[0]) >= maxRange)
  422. .sort()
  423. .reverse();
  424. if (autoRange.length) {
  425. await canvas.scene.setFlag(
  426. CONFIG.Levels.MODULE_ID,
  427. "sceneLevels",
  428. autoRange
  429. );
  430. this.loadLevels();
  431. }
  432. }
  433. computeLevelsVisibility() {
  434. WallHeight.currentTokenElevation = parseFloat(this.range[0] ?? 0);
  435. CONFIG.Levels.handlers.RefreshHandler.refreshAll();
  436. WallHeight.schedulePerceptionUpdate();
  437. }
  438. computeRangeForDocument(document, range, isTile = false) {
  439. let { rangeBottom, rangeTop } =
  440. CONFIG.Levels.helpers.getRangeForDocument(document);
  441. rangeBottom = rangeBottom ?? -Infinity;
  442. rangeTop = rangeTop ?? Infinity;
  443. range[0] = parseFloat(range[0]) ?? -Infinity;
  444. range[1] = parseFloat(range[1]) ?? Infinity;
  445. let entityRange = [rangeBottom, rangeTop];
  446. if (!isTile) {
  447. if (
  448. (entityRange[0] >= range[0] && entityRange[0] <= range[1]) ||
  449. (entityRange[1] >= range[0] && entityRange[1] <= range[1])
  450. ) {
  451. return true;
  452. } else {
  453. return false;
  454. }
  455. } else {
  456. if (entityRange[0] == range[1] + 1 && entityRange[1] == Infinity) {
  457. return true;
  458. } else {
  459. if (entityRange[0] >= range[0] && entityRange[1] <= range[1]) {
  460. return true;
  461. } else {
  462. return false;
  463. }
  464. }
  465. }
  466. }
  467. getObjUpdateData(range) {
  468. return {
  469. flags: {
  470. [`${CONFIG.Levels.MODULE_ID}`]: {
  471. rangeBottom: parseFloat(range[0]),
  472. rangeTop: parseFloat(range[1]),
  473. },
  474. },
  475. };
  476. }
  477. async elevationDialog(tool) {
  478. let content = `
  479. <div class="form-group">
  480. <label for="elevation">${game.i18n.localize(
  481. "levels.template.elevation.name"
  482. )}</label>
  483. <div class="form-fields">
  484. <input type="number" name="templateElevation" data-dtype="Number" value="${
  485. canvas.tokens.controlled[0]?.document?.elevation ?? 0
  486. }" step="1">
  487. </div>
  488. </div>
  489. <p></p>
  490. <div class="form-group">
  491. <label for="special">${game.i18n.localize(
  492. "levels.template.special.name"
  493. )}</label>
  494. <div class="form-fields">
  495. <input type="number" name="special" data-dtype="Number" value="0" step="1">
  496. </div>
  497. </div>
  498. <p></p>
  499. `;
  500. let ignoreClose = false;
  501. let toolhtml = $("body").find(`li[data-tool="setTemplateElevation"]`);
  502. let dialog = new Dialog({
  503. title: game.i18n.localize("levels.dialog.elevation.title"),
  504. content: content,
  505. buttons: {
  506. confirm: {
  507. label: game.i18n.localize("levels.yesnodialog.yes"),
  508. callback: (html) => {
  509. CONFIG.Levels.UI.nextTemplateHeight = html.find(
  510. `input[name="templateElevation"]`
  511. )[0].valueAsNumber;
  512. CONFIG.Levels.UI.nextTemplateSpecial = html.find(
  513. `input[name="special"]`
  514. )[0].valueAsNumber;
  515. CONFIG.Levels.UI.templateElevation = true;
  516. ignoreClose = true;
  517. tool.active = true;
  518. if (toolhtml[0])
  519. $("body")
  520. .find(`li[data-tool="setTemplateElevation"]`)
  521. .addClass("active");
  522. },
  523. },
  524. close: {
  525. label: game.i18n.localize("levels.yesnodialog.no"),
  526. callback: () => {
  527. CONFIG.Levels.UI.nextTemplateHeight = undefined;
  528. CONFIG.Levels.UI.nextTemplateSpecial = undefined;
  529. CONFIG.Levels.UI.templateElevation = false;
  530. tool.active = false;
  531. if (toolhtml[0])
  532. $("body")
  533. .find(`li[data-tool="setTemplateElevation"]`)
  534. .removeClass("active");
  535. },
  536. },
  537. },
  538. default: "confirm",
  539. close: () => {
  540. if (ignoreClose == true) {
  541. ignoreClose = false;
  542. return;
  543. }
  544. CONFIG.Levels.nextTemplateHeight = undefined;
  545. CONFIG.Levels.nextTemplateSpecial = undefined;
  546. CONFIG.Levels.templateElevation = false;
  547. tool.active = false;
  548. if (toolhtml[0])
  549. $("body")
  550. .find(`li[data-tool="setTemplateElevation"]`)
  551. .removeClass("active");
  552. },
  553. });
  554. await dialog._render(true);
  555. }
  556. }
  557. $(document).on("click", `li[data-control="levels"]`, (e) => {
  558. CONFIG.Levels.UI.render(true);
  559. })
  560. Hooks.on("renderSceneControls", (controls, b, c) => {
  561. if(game.user.isGM){
  562. $(".main-controls").append(`
  563. <li class="scene-control " data-control="levels" data-tooltip="${game.i18n.localize("levels.controls.main.name")}">
  564. <i class="fas fa-layer-group"></i>
  565. </li>
  566. `)
  567. }
  568. });
  569. Hooks.on("ready", () => {
  570. if (game.user.isGM) {
  571. Hooks.on("activateTilesLayer", ()=>{
  572. if(CONFIG.Levels.UI.rangeEnabled){
  573. Hooks.once("renderSceneControls", ()=>{
  574. CONFIG.Levels.UI.activateForeground();
  575. })
  576. }
  577. })
  578. Hooks.on("canvasInit", () => {
  579. CONFIG.Levels.UI.close(true);
  580. })
  581. Hooks.on("updateToken", (token,updates)=>{
  582. if("elevation" in updates)CONFIG.Levels.UI.updatePlayerList();
  583. })
  584. Hooks.on("createToken", (token,updates)=>{
  585. CONFIG.Levels.UI.updatePlayerList();
  586. })
  587. Hooks.on("deleteToken", (token,updates)=>{
  588. CONFIG.Levels.UI.updatePlayerList();
  589. })
  590. Hooks.on("controlToken", (token,controlled)=>{
  591. if(CONFIG.Levels.UI.rangeEnabled && !canvas.tokens.controlled.length){
  592. CONFIG.Levels.UI.computeLevelsVisibility();
  593. }else if(CONFIG.Levels.UI.rangeEnabled){
  594. CONFIG.Levels.handlers.RefreshHandler.refresh(canvas.tokens);
  595. }
  596. })
  597. Hooks.on("renderLevelsUI", (app, html) => {
  598. if(!app.positionSet){
  599. console.log((window.innerWidth - $("#sidebar").width() - $("#levelsUI").width()))
  600. app.setPosition({
  601. top: 2,
  602. left: (window.innerWidth - $("#sidebar").width() - $("#levelsUI").width()) - 10,
  603. width: $("#levelsUI").width(),
  604. height: Math.max(150, $("#levelsUI").height()) + 10,
  605. });
  606. app.positionSet = true
  607. }
  608. })
  609. Hooks.on("preCreateTile", (tile, updates) => {
  610. if (CONFIG.Levels.UI.rangeEnabled == true) {
  611. tile.updateSource({
  612. overhead: true,
  613. });
  614. if(!game.Levels3DPreview?._active){
  615. tile.updateSource({
  616. flags: {
  617. "betterroofs": {
  618. brMode: CONFIG.Levels.UI.roofEnabled,
  619. },
  620. [`${CONFIG.Levels.MODULE_ID}`]: {
  621. rangeBottom: CONFIG.Levels.UI.roofEnabled
  622. ? parseFloat(CONFIG.Levels.UI.range[1])
  623. : parseFloat(CONFIG.Levels.UI.range[0]),
  624. rangeTop: CONFIG.Levels.UI.roofEnabled ? Infinity : parseFloat(CONFIG.Levels.UI.range[1]),
  625. allWallBlockSight: CONFIG.Levels.UI.roofEnabled
  626. }
  627. },
  628. roof: CONFIG.Levels.UI.roofEnabled,
  629. });
  630. }else{
  631. tile.updateSource({
  632. flags: {
  633. [`${CONFIG.Levels.MODULE_ID}`]: {
  634. rangeTop: CONFIG.Levels.UI.roofEnabled ? Infinity : CONFIG.Levels.UI.range[1],
  635. }
  636. }
  637. });
  638. }
  639. if(CONFIG.Levels.UI.placeOverhead){
  640. tile.updateSource({
  641. roof: false,
  642. flags: {
  643. [`${CONFIG.Levels.MODULE_ID}`]: {
  644. showIfAbove: true,
  645. noCollision: true,
  646. showAboveRange: parseFloat(CONFIG.Levels.UI.range[1]) - parseFloat(CONFIG.Levels.UI.range[0]),
  647. rangeBottom: parseFloat(CONFIG.Levels.UI.range[1]),
  648. rangeTop: parseFloat(CONFIG.Levels.UI.range[1]),
  649. }
  650. }
  651. });
  652. }
  653. }
  654. });
  655. Hooks.on("preCreateAmbientLight", (light, updates) => {
  656. if (CONFIG.Levels.UI.rangeEnabled == true && !game.Levels3DPreview?._active) {
  657. light.updateSource(CONFIG.Levels.UI.getObjUpdateData(CONFIG.Levels.UI.range));
  658. }
  659. });
  660. Hooks.on("preCreateAmbientSound", (sound, updates) => {
  661. if (CONFIG.Levels.UI.rangeEnabled == true) {
  662. sound.updateSource(CONFIG.Levels.UI.getObjUpdateData(CONFIG.Levels.UI.range));
  663. }
  664. });
  665. Hooks.on("preCreateNote", (note, updates) => {
  666. if (CONFIG.Levels.UI.rangeEnabled == true) {
  667. note.updateSource(CONFIG.Levels.UI.getObjUpdateData(CONFIG.Levels.UI.range));
  668. }
  669. });
  670. Hooks.on("preCreateDrawing", (drawing, updates) => {
  671. let sortedLevels = [...CONFIG.Levels.UI.definedLevels].sort((a, b) => {
  672. return parseFloat(b[0]) - parseFloat(a[0])
  673. })
  674. let aboverange = sortedLevels.find(l => CONFIG.Levels.UI.range[0] === l[0] && CONFIG.Levels.UI.range[1] === l[1])
  675. aboverange = sortedLevels.indexOf(aboverange) === 0 ? undefined : sortedLevels[sortedLevels.indexOf(aboverange) - 1]
  676. if (aboverange) {
  677. let newTop = aboverange[1];
  678. let newBot = aboverange[0];
  679. if (CONFIG.Levels.UI.rangeEnabled == true) {
  680. drawing.updateSource({
  681. hidden: CONFIG.Levels.UI.stairEnabled,
  682. text: CONFIG.Levels.UI.stairEnabled ? `Levels Stair ${CONFIG.Levels.UI.range[0]}-${newBot}` : "",
  683. flags: {
  684. levels: {
  685. drawingMode: CONFIG.Levels.UI.stairEnabled ? 2 : 0,
  686. rangeBottom: parseFloat(CONFIG.Levels.UI.range[0]),
  687. rangeTop: newBot - 1,
  688. },
  689. },
  690. });
  691. }
  692. } else {
  693. if (CONFIG.Levels.UI.rangeEnabled == true) {
  694. drawing.updateSource({
  695. hidden: CONFIG.Levels.UI.stairEnabled,
  696. text: CONFIG.Levels.UI.stairEnabled ? `Levels Stair ${CONFIG.Levels.UI.range[0]}-${CONFIG.Levels.UI.range[1] + 1}` : "",
  697. flags: {
  698. levels: {
  699. drawingMode: CONFIG.Levels.UI.stairEnabled ? 2 : 0,
  700. rangeBottom: parseFloat(CONFIG.Levels.UI.range[0]),
  701. rangeTop: parseFloat(CONFIG.Levels.UI.range[1]),
  702. },
  703. },
  704. });
  705. }
  706. }
  707. });
  708. Hooks.on("preCreateWall", (wall, updates) => {
  709. if (CONFIG.Levels.UI.rangeEnabled == true) {
  710. wall.updateSource({
  711. flags: {
  712. "wall-height": {
  713. bottom: parseFloat(CONFIG.Levels.UI.range[0]),
  714. top: parseFloat(CONFIG.Levels.UI.range[1]),
  715. },
  716. },
  717. });
  718. }
  719. });
  720. Hooks.on("preCreateToken", (token, updates) => {
  721. if (CONFIG.Levels.UI.rangeEnabled == true) {
  722. if(updates) updates.elevation = parseFloat(CONFIG.Levels.UI.range[0]);
  723. token.updateSource({
  724. elevation: updates?.elevation ?? parseFloat(CONFIG.Levels.UI.range[0]),
  725. });
  726. }
  727. });
  728. }
  729. });
  730. Hooks.on("getSceneControlButtons", (controls, b, c) => {
  731. let templateTool = {
  732. name: "setTemplateElevation",
  733. title: game.i18n.localize("levels.controls.setTemplateElevation.name"),
  734. icon: "fas fa-sort",
  735. toggle: true,
  736. active: CONFIG.Levels?.UI.templateElevation || false,
  737. onClick: (toggle) => {
  738. CONFIG.Levels.UI.templateElevation = toggle;
  739. if (toggle) CONFIG.Levels.UI.elevationDialog(templateTool);
  740. else CONFIG.Levels.UI.nextTemplateHeight = undefined;
  741. },
  742. };
  743. CONFIG.Levels.UI._levelsTemplateTool = templateTool;
  744. controls.find((c) => c.name == "token").tools.push(templateTool);
  745. });
  746. Hooks.once("canvasReady", () => {
  747. console.log(
  748. `%cLEVELS\n%cWelcome to the 3rd Dimension`,
  749. "font-weight: bold;text-shadow: 10px 10px 0px rgba(0,0,0,0.8), 20px 20px 0px rgba(0,0,0,0.6), 30px 30px 0px rgba(0,0,0,0.4);font-size:100px;background: #444; color: #d43f3f; padding: 2px 28px 0 2px; display: inline-block;",
  750. "font-weight: bold;text-shadow: 2px 2px 0px rgba(0,0,0,0.8), 4px 4px 0px rgba(0,0,0,0.6), 6px 6px 0px rgba(0,0,0,0.4);font-size:20px;background: #444; color: #d43f3f; padding: 10px 27px; display: inline-block; margin-left: -30px"
  751. );
  752. });