From f6f08735ffaf794858e173b41aa894c3aec2961c Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Wed, 15 Apr 2026 00:07:13 +0200 Subject: [PATCH] Add project sequence templates --- src/sequencer/project-sequence-templates.ts | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/sequencer/project-sequence-templates.ts diff --git a/src/sequencer/project-sequence-templates.ts b/src/sequencer/project-sequence-templates.ts new file mode 100644 index 00000000..890d8add --- /dev/null +++ b/src/sequencer/project-sequence-templates.ts @@ -0,0 +1,115 @@ +import type { ProjectDialogue } from "../dialogues/project-dialogues"; +import type { InteractableEntity, TriggerVolumeEntity } from "../entities/entity-instances"; +import { + createProjectScheduleEffectFromOption, + listProjectScheduleEffectOptions, + type ProjectScheduleTargetOption +} from "../scheduler/project-schedule-control-options"; + +import { createProjectSequence, type ProjectSequence } from "./project-sequences"; + +type SequenceInteractionSource = Pick & + Partial>; + +function formatSequenceSourceLabel(source: SequenceInteractionSource): string { + const normalizedName = source.name?.trim() ?? ""; + + if (normalizedName.length > 0) { + return normalizedName; + } + + return source.kind === "interactable" ? "Interactable" : "Trigger Volume"; +} + +function resolvePreferredInteractionTargetOption( + source: SequenceInteractionSource, + targetOptions: ProjectScheduleTargetOption[] +): ProjectScheduleTargetOption | null { + if (source.kind !== "interactable") { + return null; + } + + return ( + targetOptions.find( + (targetOption) => + targetOption.target.kind === "interaction" && + targetOption.target.interactionKind === "interactable" && + targetOption.target.entityId === source.id + ) ?? null + ); +} + +function resolveFallbackImpulseTargetOption( + source: SequenceInteractionSource, + targetOptions: ProjectScheduleTargetOption[] +): ProjectScheduleTargetOption | null { + const preferredTargetOption = resolvePreferredInteractionTargetOption( + source, + targetOptions + ); + + if (preferredTargetOption !== null) { + return preferredTargetOption; + } + + return ( + targetOptions.find( + (targetOption) => listProjectScheduleEffectOptions(targetOption).length > 0 + ) ?? null + ); +} + +export function createDefaultInteractionProjectSequence(options: { + source: SequenceInteractionSource; + dialogues: ProjectDialogue[]; + targetOptions: ProjectScheduleTargetOption[]; +}): ProjectSequence { + const sourceLabel = formatSequenceSourceLabel(options.source); + const defaultDialogue = options.dialogues[0] ?? null; + + if (defaultDialogue !== null) { + return createProjectSequence({ + title: `${sourceLabel} Dialogue`, + steps: [ + { + stepClass: "impulse", + type: "startDialogue", + dialogueId: defaultDialogue.id + } + ] + }); + } + + const targetOption = resolveFallbackImpulseTargetOption( + options.source, + options.targetOptions + ); + + if (targetOption === null) { + throw new Error( + "Author a project dialogue or a sequencer-addressable control target before creating a sequence link." + ); + } + + const effectOption = listProjectScheduleEffectOptions(targetOption)[0] ?? null; + + if (effectOption === null) { + throw new Error( + "The selected control target does not expose a sequence-editable effect yet." + ); + } + + return createProjectSequence({ + title: `${sourceLabel} Sequence`, + steps: [ + { + stepClass: "impulse", + type: "controlEffect", + effect: createProjectScheduleEffectFromOption({ + targetOption, + effectOptionId: effectOption.id + }) + } + ] + }); +}