Files
Foundry-VTT-Docker/resources/app/client/pixi/layers/placeables/tiles.js
2025-01-04 00:34:03 +01:00

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;
}
}