import { describe, expect, it } from 'vitest'; import { Container, type Graphics } from 'pixi.js'; import { CreateAssetCommand, SetAssetPropertyCommand } from '../../src/core/commands/assetCommands'; import { AddTilemapLayerCommand, PaintTilesCommand } from '../../src/core/commands/tilemapCommands'; import type { TilemapAsset } from '../../src/core/model/Asset'; import { findAsset } from '../../src/core/model/Project'; import { TILE_SOLID_KEY } from '../../src/core/model/tile'; import { TilemapSolidOutlines } from '../../src/editor/viewport/tilemapSolids'; import { collectHolderItems, type DrawItem } from '../../src/viewport/items'; import { COLLIDER_FILL_ALPHA } from '../../src/viewport/symbols'; import { makeSpriteScene, place } from '../helpers'; /** A map of two layers with a solid wall tile: two cells on "Ground", one on "Roofs". */ function setup() { const f = makeSpriteScene(); const wall = new CreateAssetCommand('tile', 'Wall'); const map = new CreateAssetCommand('tilemap', 'Village'); f.stack.execute(wall); f.stack.execute(map); f.stack.execute(new SetAssetPropertyCommand(wall.asset.id, TILE_SOLID_KEY, true)); const ground = (findAsset(f.project, map.asset.id) as TilemapAsset).layers[0]!.id; const roofs = new AddTilemapLayerCommand(map.asset.id); f.stack.execute(roofs); const paint = (layerId: string, cells: [number, number][]) => f.stack.execute( new PaintTilesCommand( map.asset.id, layerId, cells.map(([x, y]) => ({ x, y, tileId: wall.asset.id })), ), ); paint(ground, [ [0, 0], [1, 0], ]); paint(roofs.layerId, [[4, 4]]); const mapObject = place(f, map.asset.id, { x: 0, y: 0 }); const item = collectHolderItems(f.project, f.sceneOf(), 0).items.find((i) => i.id === mapObject)!; return { item, ground, roofs: roofs.layerId }; } /** What the outline node of a map draws: each fill with its alpha, and how many strokes. */ function drawn(item: DrawItem, filled: Parameters[3]) { const layer = new Container(); new TilemapSolidOutlines(layer).sync(item, true, 1, filled); const node = layer.children[0] as Graphics; const instructions = node.context.instructions; return { fills: instructions .filter((i) => i.action === 'fill') .map((i) => (i.data.style as { alpha: number }).alpha), strokes: instructions.filter((i) => i.action === 'stroke').length, }; } describe('the tint on where a tilemap blocks in the scene view', () => { it('knows where one layer alone blocks', () => { const { item, ground, roofs } = setup(); expect(item.tilemap!.solids).toEqual([ { x: 0, y: 0, w: 2, h: 1 }, { x: 4, y: 4, w: 1, h: 1 }, ]); expect(item.tilemap!.layerSolids(ground).solids).toEqual([{ x: 0, y: 0, w: 2, h: 1 }]); expect(item.tilemap!.layerSolids(roofs).solids).toEqual([{ x: 4, y: 4, w: 1, h: 1 }]); expect(item.tilemap!.layerSolids('gone').solids).toEqual([]); }); it('is only an outline while nothing asks for a fill', () => { const { item } = setup(); expect(drawn(item, null)).toEqual({ fills: [], strokes: 1 }); }); it('fills the painted layer at the collider alpha and still outlines the whole map', () => { const { item, roofs } = setup(); const layerSolids = item.tilemap!.layerSolids(roofs); expect(drawn(item, layerSolids)).toEqual({ fills: [COLLIDER_FILL_ALPHA], strokes: 1 }); }); });