Let a container sort its content with its parent (#243)
A container is not drawn, so it had no place in the draw order. The chain from its content to the parent of the container broke there, and a container under a character could not be drawn together with it. - A container gets the switch Sort with parent under Z. It defaults to off, also for new containers, so projects draw as they did. - With the switch on, the whole content is drawn in the slot of the container's parent. The content's own switch does not count there. - Containers in a row pass the content on as long as each has the switch on. On the top level or under a tilemap it has no effect. - drawSlots follows the new sortsWith field of a draw item. Scene view, game and picking all use drawSlots, so all three agree. Found on the way: a scene object hung directly under a container spells the container out, and its template content is dropped. That is the existing spelledOut rule, the tests now keep both cases apart. Tests: - ./check.sh: 178 files, 2573 tests, CHECK EXIT 0 - 7 new unit tests in sort-with-parent.test.ts: the switch, template content, override per container, a chain, top level and tilemap, game and picking - e2e 5b-iii: a real click on the switch in the inspector, with undo - 4 counter-probes over #242 and #243 bite (2 unit, 2 browser) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
074bda2247
commit
3abb6894a7
5 files changed
+188
-15
No files matched your search
@@ -4,13 +4,14 @@ import {
|
||||
ResetAssetPropertyCommand,
|
||||
SetAssetPropertyCommand,
|
||||
} from '../../src/core/commands/assetCommands';
|
||||
import { SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
|
||||
import {
|
||||
PaintTilesCommand,
|
||||
RenameTilemapLayerCommand,
|
||||
SetTilemapLayerSortByYCommand,
|
||||
} from '../../src/core/commands/tilemapCommands';
|
||||
import { PlaceAssetCommand, SetOverrideCommand } from '../../src/core/commands/sceneObjectCommands';
|
||||
import type { TilemapAsset } from '../../src/core/model/Asset';
|
||||
import { instanceChildId } from '../../src/core/model/instance';
|
||||
import type { AssetType } from '../../src/core/model/AssetTypes';
|
||||
import { findAsset } from '../../src/core/model/Project';
|
||||
import { schemaFor, SORT_WITH_PARENT_KEY } from '../../src/core/properties/PropertySchema';
|
||||
@@ -55,7 +56,7 @@ describe('a child sorted with its parent (#232)', () => {
|
||||
type,
|
||||
).toBe(true);
|
||||
}
|
||||
for (const type of ['tilemap', 'container', 'camera', 'collider', 'behavior'] as const)
|
||||
for (const type of ['tilemap', 'camera', 'collider', 'behavior'] as const)
|
||||
expect(
|
||||
schemaFor(type).some((d) => d.key === SORT_WITH_PARENT_KEY),
|
||||
type,
|
||||
@@ -204,3 +205,96 @@ describe('a child sorted with its parent (#232)', () => {
|
||||
expect(stackAt(items, hits).map((id) => s.names.get(id))).toEqual(['box', 'weapon', 'other']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('a container sorted with its parent (#243)', () => {
|
||||
/**
|
||||
* "other" and "box" on 0, a container "group" below the box, and in the container's template a
|
||||
* "weapon" on -1.
|
||||
*/
|
||||
function withContainer() {
|
||||
const s = makeScene();
|
||||
const groupAsset = s.asset('container', 'Group');
|
||||
const inTemplate = new PlaceAssetCommand(groupAsset, s.weaponId, { x: 0, y: 0, z: -1 }, '');
|
||||
s.f.stack.execute(inTemplate);
|
||||
s.put('other', s.f.spriteId, { x: 300, y: 0, z: 0 });
|
||||
const box = s.put('box', s.f.spriteId, { x: 0, y: 0, z: 0 });
|
||||
const group = s.put('group', groupAsset, { x: 0, y: 0, z: 0 }, box);
|
||||
s.names.set(instanceChildId(group, inTemplate.objectId), 'weapon');
|
||||
const sortGroup = (on: boolean) =>
|
||||
s.f.stack.execute(new SetAssetPropertyCommand(groupAsset, SORT_WITH_PARENT_KEY, on));
|
||||
return { ...s, groupAsset, group, box, sortGroup };
|
||||
}
|
||||
|
||||
it('has the switch, off for a new container', () => {
|
||||
const s = makeScene();
|
||||
const id = s.asset('container', 'Group');
|
||||
expect(schemaFor('container').some((d) => d.key === SORT_WITH_PARENT_KEY)).toBe(true);
|
||||
expect(resolveAssetProps(s.f.project, findAsset(s.f.project, id)!)[SORT_WITH_PARENT_KEY]).toBe(false);
|
||||
});
|
||||
|
||||
it('draws its content in the place of its parent once switched on', () => {
|
||||
const s = withContainer();
|
||||
expect(s.order()).toEqual(['weapon', 'other', 'box']);
|
||||
s.sortGroup(true);
|
||||
expect(s.order()).toEqual(['other', 'weapon', 'box']);
|
||||
s.f.stack.undo();
|
||||
expect(s.order()).toEqual(['weapon', 'other', 'box']);
|
||||
});
|
||||
|
||||
it('passes content on whatever the content says itself', () => {
|
||||
const s = withContainer();
|
||||
s.sortWithParent(s.weaponId, false);
|
||||
s.sortGroup(true);
|
||||
expect(s.order()).toEqual(['other', 'weapon', 'box']);
|
||||
});
|
||||
|
||||
it('can be switched per placed container', () => {
|
||||
const s = withContainer();
|
||||
s.f.stack.execute(new SetOverrideCommand(s.f.sceneId, s.group, SORT_WITH_PARENT_KEY, true));
|
||||
expect(s.order()).toEqual(['other', 'weapon', 'box']);
|
||||
});
|
||||
|
||||
it('passes through unpacked containers in a row, and stops at one that is off', () => {
|
||||
const s = makeScene();
|
||||
const outerAsset = s.asset('container', 'Outer');
|
||||
const innerAsset = s.asset('container', 'Inner');
|
||||
s.put('other', s.f.spriteId, { x: 300, y: 0, z: 0 });
|
||||
const box = s.put('box', s.f.spriteId, { x: 0, y: 0, z: 0 });
|
||||
const outer = s.put('outer', outerAsset, { x: 0, y: 0, z: 0 }, box);
|
||||
const inner = s.put('inner', innerAsset, { x: 0, y: 0, z: 0 }, outer);
|
||||
s.put('weapon', s.weaponId, { x: 0, y: 0, z: -1 }, inner);
|
||||
s.f.stack.execute(new SetAssetPropertyCommand(innerAsset, SORT_WITH_PARENT_KEY, true));
|
||||
expect(s.order()).toEqual(['weapon', 'other', 'box']);
|
||||
s.f.stack.execute(new SetAssetPropertyCommand(outerAsset, SORT_WITH_PARENT_KEY, true));
|
||||
expect(s.order()).toEqual(['other', 'weapon', 'box']);
|
||||
});
|
||||
|
||||
it('draws alone at the top level, and not with a tilemap', () => {
|
||||
const s = makeScene();
|
||||
const groupAsset = s.asset('container', 'Group');
|
||||
const map = s.put('map', s.asset('tilemap', 'Map'), { x: 0, y: 0, z: 0 });
|
||||
const top = s.put('top', groupAsset, { x: 0, y: 0, z: 0 });
|
||||
const underMap = s.put('underMap', groupAsset, { x: 0, y: 0, z: 0 }, map);
|
||||
s.put('a', s.weaponId, { x: 0, y: 0, z: 1 }, top);
|
||||
s.put('b', s.weaponId, { x: 0, y: 0, z: -1 }, underMap);
|
||||
s.f.stack.execute(new SetAssetPropertyCommand(groupAsset, SORT_WITH_PARENT_KEY, true));
|
||||
expect(s.order()).toEqual(['b', 'map', 'map', 'a']);
|
||||
});
|
||||
|
||||
it('holds in the running game and when picking', () => {
|
||||
const s = withContainer();
|
||||
s.sortGroup(true);
|
||||
const { world } = startWorld(s.f);
|
||||
expect(drawSlots(collectWorldItems(world).items).map((slot) => s.names.get(slot.id))).toEqual([
|
||||
'other',
|
||||
'weapon',
|
||||
'box',
|
||||
]);
|
||||
const items = collectHolderItems(s.f.project, s.f.sceneOf(), 0).items;
|
||||
const hits = ['other', 'box', 'weapon'].map((name) => ({
|
||||
id: [...s.names].find(([, n]) => n === name)![0],
|
||||
layers: null,
|
||||
}));
|
||||
expect(stackAt(items, hits).map((id) => s.names.get(id))).toEqual(['box', 'weapon', 'other']);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user