255 lines
7.8 KiB
JavaScript
255 lines
7.8 KiB
JavaScript
|
|
/**
|
||
|
|
* A PlaceablesLayer designed for rendering the visual Scene for a specific vertical cross-section.
|
||
|
|
* @category - Canvas
|
||
|
|
*/
|
||
|
|
class TilesLayer extends PlaceablesLayer {
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
static documentName = "Tile";
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
/* Layer Attributes */
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
static get layerOptions() {
|
||
|
|
return foundry.utils.mergeObject(super.layerOptions, {
|
||
|
|
name: "tiles",
|
||
|
|
zIndex: 300,
|
||
|
|
controllableObjects: true,
|
||
|
|
rotatableObjects: true
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
get hookName() {
|
||
|
|
return TilesLayer.name;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
get hud() {
|
||
|
|
return canvas.hud.tile;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* An array of Tile objects which are rendered within the objects container
|
||
|
|
* @type {Tile[]}
|
||
|
|
*/
|
||
|
|
get tiles() {
|
||
|
|
return this.objects?.children || [];
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @override */
|
||
|
|
*controllableObjects() {
|
||
|
|
const foreground = ui.controls.control.foreground ?? false;
|
||
|
|
for ( const placeable of super.controllableObjects() ) {
|
||
|
|
const overhead = placeable.document.elevation >= placeable.document.parent.foregroundElevation;
|
||
|
|
if ( overhead === foreground ) yield placeable;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
/* Layer Methods */
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritDoc */
|
||
|
|
getSnappedPoint(point) {
|
||
|
|
if ( canvas.forceSnapVertices ) return canvas.grid.getSnappedPoint(point, {mode: CONST.GRID_SNAPPING_MODES.VERTEX});
|
||
|
|
return super.getSnappedPoint(point);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritDoc */
|
||
|
|
async _tearDown(options) {
|
||
|
|
for ( const tile of this.tiles ) {
|
||
|
|
if ( tile.isVideo ) {
|
||
|
|
game.video.stop(tile.sourceElement);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return super._tearDown(options);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
/* Event Handlers */
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
_onDragLeftStart(event) {
|
||
|
|
super._onDragLeftStart(event);
|
||
|
|
const interaction = event.interactionData;
|
||
|
|
|
||
|
|
// Snap the origin to the grid
|
||
|
|
if ( !event.shiftKey ) interaction.origin = this.getSnappedPoint(interaction.origin);
|
||
|
|
|
||
|
|
// Create the preview
|
||
|
|
const tile = this.constructor.placeableClass.createPreview(interaction.origin);
|
||
|
|
interaction.preview = this.preview.addChild(tile);
|
||
|
|
this.preview._creating = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
_onDragLeftMove(event) {
|
||
|
|
const interaction = event.interactionData;
|
||
|
|
|
||
|
|
// Snap the destination to the grid
|
||
|
|
if ( !event.shiftKey ) interaction.destination = this.getSnappedPoint(interaction.destination);
|
||
|
|
|
||
|
|
const {destination, tilesState, preview, origin} = interaction;
|
||
|
|
if ( tilesState === 0 ) return;
|
||
|
|
|
||
|
|
// Determine the drag distance
|
||
|
|
const dx = destination.x - origin.x;
|
||
|
|
const dy = destination.y - origin.y;
|
||
|
|
const dist = Math.min(Math.abs(dx), Math.abs(dy));
|
||
|
|
|
||
|
|
// Update the preview object
|
||
|
|
preview.document.width = (event.altKey ? dist * Math.sign(dx) : dx);
|
||
|
|
preview.document.height = (event.altKey ? dist * Math.sign(dy) : dy);
|
||
|
|
preview.renderFlags.set({refreshSize: true});
|
||
|
|
|
||
|
|
// Confirm the creation state
|
||
|
|
interaction.tilesState = 2;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
_onDragLeftDrop(event) {
|
||
|
|
// Snap the destination to the grid
|
||
|
|
const interaction = event.interactionData;
|
||
|
|
if ( !event.shiftKey ) interaction.destination = this.getSnappedPoint(interaction.destination);
|
||
|
|
|
||
|
|
const { tilesState, preview } = interaction;
|
||
|
|
if ( tilesState !== 2 ) return;
|
||
|
|
const doc = preview.document;
|
||
|
|
|
||
|
|
// Re-normalize the dropped shape
|
||
|
|
const r = new PIXI.Rectangle(doc.x, doc.y, doc.width, doc.height).normalize();
|
||
|
|
preview.document.updateSource(r);
|
||
|
|
|
||
|
|
// Require a minimum created size
|
||
|
|
if ( Math.hypot(r.width, r.height) < (canvas.dimensions.size / 2) ) return;
|
||
|
|
|
||
|
|
// Render the preview sheet for confirmation
|
||
|
|
preview.sheet.render(true, {preview: true});
|
||
|
|
this.preview._creating = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/** @inheritdoc */
|
||
|
|
_onDragLeftCancel(event) {
|
||
|
|
if ( this.preview._creating ) return;
|
||
|
|
return super._onDragLeftCancel(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Handle drop events for Tile data on the Tiles Layer
|
||
|
|
* @param {DragEvent} event The concluding drag event
|
||
|
|
* @param {object} data The extracted Tile data
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
async _onDropData(event, data) {
|
||
|
|
if ( !data.texture?.src ) return;
|
||
|
|
if ( !this.active ) this.activate();
|
||
|
|
|
||
|
|
// Get the data for the tile to create
|
||
|
|
const createData = await this._getDropData(event, data);
|
||
|
|
|
||
|
|
// Validate that the drop position is in-bounds and snap to grid
|
||
|
|
if ( !canvas.dimensions.rect.contains(createData.x, createData.y) ) return false;
|
||
|
|
|
||
|
|
// Create the Tile Document
|
||
|
|
const cls = getDocumentClass(this.constructor.documentName);
|
||
|
|
return cls.create(createData, {parent: canvas.scene});
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Prepare the data object when a new Tile is dropped onto the canvas
|
||
|
|
* @param {DragEvent} event The concluding drag event
|
||
|
|
* @param {object} data The extracted Tile data
|
||
|
|
* @returns {object} The prepared data to create
|
||
|
|
*/
|
||
|
|
async _getDropData(event, data) {
|
||
|
|
|
||
|
|
// Determine the tile size
|
||
|
|
const tex = await loadTexture(data.texture.src);
|
||
|
|
const ratio = canvas.dimensions.size / (data.tileSize || canvas.dimensions.size);
|
||
|
|
data.width = tex.baseTexture.width * ratio;
|
||
|
|
data.height = tex.baseTexture.height * ratio;
|
||
|
|
|
||
|
|
// Determine the elevation
|
||
|
|
const foreground = ui.controls.controls.find(c => c.layer === "tiles").foreground;
|
||
|
|
data.elevation = foreground ? canvas.scene.foregroundElevation : 0;
|
||
|
|
data.sort = Math.max(this.getMaxSort() + 1, 0);
|
||
|
|
foundry.utils.setProperty(data, "occlusion.mode", foreground
|
||
|
|
? CONST.OCCLUSION_MODES.FADE : CONST.OCCLUSION_MODES.NONE);
|
||
|
|
|
||
|
|
// Determine the final position and snap to grid unless SHIFT is pressed
|
||
|
|
data.x = data.x - (data.width / 2);
|
||
|
|
data.y = data.y - (data.height / 2);
|
||
|
|
if ( !event.shiftKey ) {
|
||
|
|
const {x, y} = this.getSnappedPoint(data);
|
||
|
|
data.x = x;
|
||
|
|
data.y = y;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create the tile as hidden if the ALT key is pressed
|
||
|
|
if ( event.altKey ) data.hidden = true;
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
/* Deprecations and Compatibility */
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @deprecated since v12
|
||
|
|
* @ignore
|
||
|
|
*/
|
||
|
|
get roofs() {
|
||
|
|
const msg = "TilesLayer#roofs has been deprecated without replacement.";
|
||
|
|
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
|
||
|
|
return this.placeables.filter(t => t.isRoof);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @deprecated since v11
|
||
|
|
* @ignore
|
||
|
|
*/
|
||
|
|
get textureDataMap() {
|
||
|
|
const msg = "TilesLayer#textureDataMap has moved to TextureLoader.textureBufferDataMap";
|
||
|
|
foundry.utils.logCompatibilityWarning(msg, {since: 11, until: 13});
|
||
|
|
return TextureLoader.textureBufferDataMap;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* -------------------------------------------- */
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @deprecated since v11
|
||
|
|
* @ignore
|
||
|
|
*/
|
||
|
|
get depthMask() {
|
||
|
|
const msg = "TilesLayer#depthMask is deprecated without replacement. Use canvas.masks.depth instead";
|
||
|
|
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
|
||
|
|
return canvas.masks.depth;
|
||
|
|
}
|
||
|
|
}
|