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.

750 lines
25 KiB

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