Files
webeditor3d/src/sequencer/project-sequence-steps.ts

437 lines
12 KiB
TypeScript

import {
cloneControlEffect,
getControlEffectLabel,
type ControlEffect
} from "../controls/control-surface";
import {
getInteractionActionControlEffect,
type InteractionLink
} from "../interactions/interaction-links";
import type { ProjectScheduleRoutine } from "../scheduler/project-scheduler";
export interface HeldControlSequenceEffect {
stepClass: "held";
type: "controlEffect";
effect: ControlEffect;
}
export interface ImpulseControlSequenceEffect {
stepClass: "impulse";
type: "controlEffect";
effect: ControlEffect;
}
export interface StartDialogueSequenceEffect {
stepClass: "impulse";
type: "startDialogue";
dialogueId: string;
}
export interface TeleportPlayerSequenceEffect {
stepClass: "impulse";
type: "teleportPlayer";
targetEntityId: string;
}
export interface StartSceneTransitionSequenceEffect {
stepClass: "impulse";
type: "startSceneTransition";
targetSceneId: string;
targetEntryEntityId: string;
}
export const SEQUENCE_VISIBILITY_MODES = ["toggle", "show", "hide"] as const;
export type SequenceVisibilityMode = (typeof SEQUENCE_VISIBILITY_MODES)[number];
export interface BrushSequenceVisibilityTarget {
kind: "brush";
brushId: string;
}
export interface ModelInstanceSequenceVisibilityTarget {
kind: "modelInstance";
modelInstanceId: string;
}
export type SequenceVisibilityTarget =
| BrushSequenceVisibilityTarget
| ModelInstanceSequenceVisibilityTarget;
export interface SetVisibilitySequenceEffect {
stepClass: "impulse";
type: "setVisibility";
target: SequenceVisibilityTarget;
mode: SequenceVisibilityMode;
}
type SequenceDefinitionLike = {
id: string;
effects: SequenceEffect[];
};
type SequenceLibraryLike = {
sequences: Record<string, SequenceDefinitionLike>;
};
export type HeldSequenceStep = HeldControlSequenceEffect;
export type ImpulseSequenceStep =
| ImpulseControlSequenceEffect
| StartDialogueSequenceEffect
| TeleportPlayerSequenceEffect
| StartSceneTransitionSequenceEffect
| SetVisibilitySequenceEffect;
export type SequenceEffect = HeldSequenceStep | ImpulseSequenceStep;
export type SequenceClip = SequenceEffect;
export type SequenceStep = SequenceEffect;
export function cloneSequenceEffect(effect: SequenceEffect): SequenceEffect {
switch (effect.type) {
case "controlEffect":
return {
stepClass: effect.stepClass,
type: "controlEffect",
effect: cloneControlEffect(effect.effect)
};
case "startDialogue":
return {
stepClass: "impulse",
type: "startDialogue",
dialogueId: effect.dialogueId
};
case "teleportPlayer":
return {
stepClass: "impulse",
type: "teleportPlayer",
targetEntityId: effect.targetEntityId
};
case "startSceneTransition":
return {
stepClass: "impulse",
type: "startSceneTransition",
targetSceneId: effect.targetSceneId,
targetEntryEntityId: effect.targetEntryEntityId
};
case "setVisibility":
return {
stepClass: "impulse",
type: "setVisibility",
target:
effect.target.kind === "brush"
? {
kind: "brush",
brushId: effect.target.brushId
}
: {
kind: "modelInstance",
modelInstanceId: effect.target.modelInstanceId
},
mode: effect.mode
};
}
}
export function cloneSequenceClip(clip: SequenceClip): SequenceClip {
return cloneSequenceEffect(clip);
}
export function cloneSequenceStep(step: SequenceStep): SequenceStep {
return cloneSequenceEffect(step);
}
export function cloneSequenceEffects(effects: SequenceEffect[]): SequenceEffect[] {
return effects.map(cloneSequenceEffect);
}
export function cloneSequenceClips(clips: SequenceClip[]): SequenceClip[] {
return cloneSequenceEffects(clips);
}
export function cloneSequenceSteps(steps: SequenceStep[]): SequenceStep[] {
return cloneSequenceEffects(steps);
}
export function getSequenceEffectLabel(effect: SequenceEffect): string {
switch (effect.type) {
case "controlEffect":
return getControlEffectLabel(effect.effect);
case "startDialogue":
return "Start Dialogue";
case "teleportPlayer":
return "Teleport Player";
case "startSceneTransition":
return "Change Scene";
case "setVisibility":
switch (effect.mode) {
case "show":
return "Show";
case "hide":
return "Hide";
case "toggle":
return "Toggle Visibility";
}
}
}
export function getSequenceClipLabel(clip: SequenceClip): string {
return getSequenceEffectLabel(clip);
}
export function getSequenceStepLabel(step: SequenceStep): string {
return getSequenceEffectLabel(step);
}
export function getHeldSequenceEffects(
effects: readonly SequenceEffect[]
): HeldSequenceStep[] {
return effects
.filter((effect): effect is HeldSequenceStep => effect.stepClass === "held")
.map(cloneSequenceEffect) as HeldSequenceStep[];
}
export function getHeldSequenceClips(
clips: readonly SequenceClip[]
): HeldSequenceStep[] {
return getHeldSequenceEffects(clips);
}
export function getHeldSequenceSteps(
steps: readonly SequenceStep[]
): HeldSequenceStep[] {
return getHeldSequenceEffects(steps);
}
export function getImpulseSequenceEffects(
effects: readonly SequenceEffect[]
): ImpulseSequenceStep[] {
return effects
.filter((effect): effect is ImpulseSequenceStep => effect.stepClass === "impulse")
.map(cloneSequenceEffect) as ImpulseSequenceStep[];
}
export function getImpulseSequenceClips(
clips: readonly SequenceClip[]
): ImpulseSequenceStep[] {
return getImpulseSequenceEffects(clips);
}
export function getImpulseSequenceSteps(
steps: readonly SequenceStep[]
): ImpulseSequenceStep[] {
return getImpulseSequenceEffects(steps);
}
export function getProjectSequenceHeldEffects(
sequence: SequenceDefinitionLike
): HeldSequenceStep[] {
return getHeldSequenceEffects(sequence.effects);
}
export function getProjectSequenceHeldClips(
sequence: SequenceDefinitionLike
): HeldSequenceStep[] {
return getProjectSequenceHeldEffects(sequence);
}
export function getProjectSequenceHeldSteps(
sequence: SequenceDefinitionLike
): HeldSequenceStep[] {
return getProjectSequenceHeldEffects(sequence);
}
export function getProjectSequenceImpulseEffects(
sequence: SequenceDefinitionLike
): ImpulseSequenceStep[] {
return getImpulseSequenceEffects(sequence.effects);
}
export function getProjectSequenceImpulseClips(
sequence: SequenceDefinitionLike
): ImpulseSequenceStep[] {
return getProjectSequenceImpulseEffects(sequence);
}
export function getProjectSequenceImpulseSteps(
sequence: SequenceDefinitionLike
): ImpulseSequenceStep[] {
return getProjectSequenceImpulseEffects(sequence);
}
export function getInteractionLinkImpulseEffects(
link: InteractionLink,
sequenceLibrary?: SequenceLibraryLike | null
): ImpulseSequenceStep[] {
const controlEffect = getInteractionActionControlEffect(link.action);
if (controlEffect !== null) {
return [
{
stepClass: "impulse",
type: "controlEffect",
effect: controlEffect
}
];
}
switch (link.action.type) {
case "teleportPlayer":
return [
{
stepClass: "impulse",
type: "teleportPlayer",
targetEntityId: link.action.targetEntityId
}
];
case "toggleVisibility":
return [
{
stepClass: "impulse",
type: "setVisibility",
target: {
kind: "brush",
brushId: link.action.targetBrushId
},
mode:
link.action.visible === undefined
? "toggle"
: link.action.visible
? "show"
: "hide"
}
];
case "startDialogue":
return [
{
stepClass: "impulse",
type: "startDialogue",
dialogueId: link.action.dialogueId
}
];
case "runSequence": {
const sequence =
sequenceLibrary?.sequences[link.action.sequenceId] ?? null;
return sequence === null ? [] : getProjectSequenceImpulseEffects(sequence);
}
case "playAnimation":
case "stopAnimation":
case "playSound":
case "stopSound":
case "control":
throw new Error(
`Interaction action ${link.action.type} should have normalized to a controlEffect sequence effect.`
);
}
}
export function getInteractionLinkImpulseClips(
link: InteractionLink,
sequenceLibrary?: SequenceLibraryLike | null
): ImpulseSequenceStep[] {
return getInteractionLinkImpulseEffects(link, sequenceLibrary);
}
export function getInteractionLinkImpulseSteps(
link: InteractionLink,
sequenceLibrary?: SequenceLibraryLike | null
): ImpulseSequenceStep[] {
return getInteractionLinkImpulseEffects(link, sequenceLibrary);
}
export function getInteractionLinkSequenceEffects(
link: InteractionLink,
sequenceLibrary?: SequenceLibraryLike | null
): SequenceEffect[] {
return getInteractionLinkImpulseEffects(link, sequenceLibrary);
}
export function getInteractionLinkSequenceClips(
link: InteractionLink,
sequenceLibrary?: SequenceLibraryLike | null
): SequenceClip[] {
return getInteractionLinkSequenceEffects(link, sequenceLibrary);
}
export function getInteractionLinkSequenceSteps(
link: InteractionLink,
sequenceLibrary?: SequenceLibraryLike | null
): SequenceStep[] {
return getInteractionLinkSequenceEffects(link, sequenceLibrary);
}
export function getProjectScheduleRoutineHeldEffects(
routine: ProjectScheduleRoutine,
sequenceLibrary?: SequenceLibraryLike | null
): HeldSequenceStep[] {
if (routine.sequenceId !== null) {
const sequence = sequenceLibrary?.sequences[routine.sequenceId] ?? null;
if (sequence !== null) {
return getProjectSequenceHeldEffects(sequence);
}
}
return routine.effects.map((effect) => ({
stepClass: "held" as const,
type: "controlEffect" as const,
effect: cloneControlEffect(effect)
}));
}
export function getProjectScheduleRoutineHeldClips(
routine: ProjectScheduleRoutine,
sequenceLibrary?: SequenceLibraryLike | null
): HeldSequenceStep[] {
return getProjectScheduleRoutineHeldEffects(routine, sequenceLibrary);
}
export function getProjectScheduleRoutineHeldSteps(
routine: ProjectScheduleRoutine,
sequenceLibrary?: SequenceLibraryLike | null
): HeldSequenceStep[] {
return getProjectScheduleRoutineHeldEffects(routine, sequenceLibrary);
}
export function getProjectScheduleRoutineSequenceEffects(
routine: ProjectScheduleRoutine,
sequenceLibrary?: SequenceLibraryLike | null
): SequenceEffect[] {
return getProjectScheduleRoutineHeldEffects(routine, sequenceLibrary);
}
export function getProjectScheduleRoutineSequenceClips(
routine: ProjectScheduleRoutine,
sequenceLibrary?: SequenceLibraryLike | null
): SequenceClip[] {
return getProjectScheduleRoutineSequenceEffects(routine, sequenceLibrary);
}
export function getProjectScheduleRoutineSequenceSteps(
routine: ProjectScheduleRoutine,
sequenceLibrary?: SequenceLibraryLike | null
): SequenceStep[] {
return getProjectScheduleRoutineSequenceEffects(routine, sequenceLibrary);
}
export function getHeldSequenceControlEffects(
steps: readonly HeldSequenceStep[]
): ControlEffect[] {
return steps
.filter((step): step is HeldControlSequenceEffect => step.type === "controlEffect")
.map((step) => cloneControlEffect(step.effect));
}
export function findHeldSequenceControlEffect<
TType extends ControlEffect["type"]
>(
steps: readonly HeldSequenceStep[],
type: TType
): Extract<ControlEffect, { type: TType }> | null {
return (
getHeldSequenceControlEffects(steps).find(
(effect): effect is Extract<ControlEffect, { type: TType }> =>
effect.type === type
) ?? null
);
}