Autotiles are a new asset type with a tab that shows every tile of the floor (2x3) or wall (2x2) block and an animated sample pattern (#118)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c17b2b5426
commit
1a9f07c4ac
24 files changed
+1797
-18
No files matched your search
@@ -39,6 +39,8 @@ export function buildAsset(type: AssetType, name: string, id = newId()): Asset {
|
||||
return { ...base, type: 'container', objects: [] };
|
||||
case 'animation':
|
||||
return { ...base, type: 'animation', root: '', tracks: [] };
|
||||
case 'autotile':
|
||||
return { ...base, type: 'autotile', pictures: [] };
|
||||
case 'statemachine':
|
||||
return { ...base, type: 'statemachine', graph: createEmptyGraph() };
|
||||
case 'shader':
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* The picture list of an autotile (#118): which blocks it is drawn from, and in which order – the
|
||||
* order is the animation, so moving a picture is an edit of its own rather than a cosmetic sort.
|
||||
* The kind, the rate and the playback are ordinary properties and go through `SetAssetPropertyCommand`.
|
||||
*/
|
||||
import type { Project } from '../model/Project';
|
||||
import { resolvePicture } from '../model/texture';
|
||||
import { type Command, CommandError } from './Command';
|
||||
import { requireAutotile } from './require';
|
||||
|
||||
/** Appends a picture; `at` puts it before the picture with that index instead. */
|
||||
export class AddAutotilePictureCommand implements Command {
|
||||
readonly label = 'Add autotile picture';
|
||||
private index = -1;
|
||||
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
private readonly pictureId: string,
|
||||
private readonly at?: number,
|
||||
) {}
|
||||
|
||||
execute(project: Project): void {
|
||||
const asset = requireAutotile(project, this.assetId);
|
||||
// A texture or one of its frames – anything else names no block, and a list of dead ids is
|
||||
// the one thing the tab could not explain to whoever built it.
|
||||
if (!resolvePicture(project, this.pictureId))
|
||||
throw new CommandError(`${this.pictureId} is not a texture or a frame`);
|
||||
this.index = this.at === undefined ? asset.pictures.length : clampIndex(this.at, asset.pictures.length);
|
||||
asset.pictures.splice(this.index, 0, this.pictureId);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
requireAutotile(project, this.assetId).pictures.splice(this.index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveAutotilePictureCommand implements Command {
|
||||
readonly label = 'Remove autotile picture';
|
||||
private removed = '';
|
||||
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
private readonly index: number,
|
||||
) {}
|
||||
|
||||
execute(project: Project): void {
|
||||
const asset = requireAutotile(project, this.assetId);
|
||||
const picture = asset.pictures[this.index];
|
||||
if (picture === undefined) throw new CommandError(`${asset.name} has no picture ${this.index + 1}`);
|
||||
this.removed = picture;
|
||||
asset.pictures.splice(this.index, 1);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
requireAutotile(project, this.assetId).pictures.splice(this.index, 0, this.removed);
|
||||
}
|
||||
}
|
||||
|
||||
/** Moves one picture to another place in the list; the step in between is what changes the animation. */
|
||||
export class MoveAutotilePictureCommand implements Command {
|
||||
readonly label = 'Reorder autotile pictures';
|
||||
|
||||
constructor(
|
||||
private readonly assetId: string,
|
||||
private readonly from: number,
|
||||
private readonly to: number,
|
||||
) {}
|
||||
|
||||
execute(project: Project): void {
|
||||
this.move(project, this.from, this.to);
|
||||
}
|
||||
|
||||
undo(project: Project): void {
|
||||
this.move(project, this.to, this.from);
|
||||
}
|
||||
|
||||
private move(project: Project, from: number, to: number): void {
|
||||
const asset = requireAutotile(project, this.assetId);
|
||||
const picture = asset.pictures[from];
|
||||
if (picture === undefined) throw new CommandError(`${asset.name} has no picture ${from + 1}`);
|
||||
if (to < 0 || to >= asset.pictures.length)
|
||||
throw new CommandError(`${asset.name} has no place ${to + 1} to move it to`);
|
||||
asset.pictures.splice(from, 1);
|
||||
asset.pictures.splice(to, 0, picture);
|
||||
}
|
||||
}
|
||||
|
||||
function clampIndex(index: number, length: number): number {
|
||||
if (!Number.isFinite(index)) return length;
|
||||
return Math.max(0, Math.min(length, Math.floor(index)));
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Lookups that throw a `CommandError` when the target does not exist. */
|
||||
import { findNode, type Graph, type GraphEdge, type GraphNode } from '../graph/Graph';
|
||||
import { type NodeDef, nodeDef } from '../graph/NodeRegistry';
|
||||
import type { Asset, BehaviorAsset, StateMachineAsset, VariablesAsset } from '../model/Asset';
|
||||
import type { Asset, AutotileAsset, BehaviorAsset, StateMachineAsset, VariablesAsset } from '../model/Asset';
|
||||
import { findObject, type ObjectTree } from '../model/hierarchy';
|
||||
import { isTemplateContent, objectRef, type ObjectRef } from '../model/instance';
|
||||
import {
|
||||
@@ -79,6 +79,12 @@ export function requireVariableOwner(project: Project, assetId: string): Behavio
|
||||
return asset;
|
||||
}
|
||||
|
||||
export function requireAutotile(project: Project, assetId: string): AutotileAsset {
|
||||
const asset = findAssetOf(project, assetId, 'autotile');
|
||||
if (!asset) throw new CommandError(`Autotile ${assetId} not found`);
|
||||
return asset;
|
||||
}
|
||||
|
||||
export function requireStateMachine(project: Project, assetId: string): StateMachineAsset {
|
||||
const asset = findAssetOf(project, assetId, 'statemachine');
|
||||
if (!asset) throw new CommandError(`State machine ${assetId} not found`);
|
||||
|
||||
Reference in new issue
Block a user