Add project sequence steps and update related files
This commit is contained in:
@@ -32,7 +32,8 @@ import {
|
||||
type ProjectSequenceLibrary
|
||||
} from "../sequencer/project-sequences";
|
||||
|
||||
export const SCENE_DOCUMENT_VERSION = 55 as const;
|
||||
export const SCENE_DOCUMENT_VERSION = 56 as const;
|
||||
export const PROJECT_SEQUENCE_EFFECTS_SCENE_DOCUMENT_VERSION = 56 as const;
|
||||
export const PROJECT_SEQUENCE_TIMING_SCENE_DOCUMENT_VERSION = 55 as const;
|
||||
export const PROJECT_SEQUENCE_CLIPS_SCENE_DOCUMENT_VERSION = 54 as const;
|
||||
export const PROJECT_SEQUENCE_LIBRARY_SCENE_DOCUMENT_VERSION = 53 as const;
|
||||
|
||||
@@ -28,8 +28,8 @@ import {
|
||||
} from "../sequencer/project-sequencer";
|
||||
import {
|
||||
findHeldSequenceControlEffect,
|
||||
getProjectScheduleRoutineHeldSteps,
|
||||
getProjectScheduleRoutineResolvedHeldControlEffectsAtMinute
|
||||
getHeldSequenceControlEffects,
|
||||
getProjectScheduleRoutineHeldSteps
|
||||
} from "../sequencer/project-sequence-steps";
|
||||
import type { ProjectSequenceLibrary } from "../sequencer/project-sequences";
|
||||
|
||||
@@ -431,42 +431,23 @@ export function resolveRuntimeActorScheduleState(options: {
|
||||
activeRoutine,
|
||||
options.sequences
|
||||
);
|
||||
const elapsedHours = getProjectScheduleRoutineElapsedHoursAt(
|
||||
activeRoutine,
|
||||
options.dayNumber,
|
||||
options.timeOfDayHours
|
||||
);
|
||||
const resolvedHeldEffects =
|
||||
elapsedHours === null
|
||||
? []
|
||||
: getProjectScheduleRoutineResolvedHeldControlEffectsAtMinute(
|
||||
activeRoutine,
|
||||
options.sequences,
|
||||
elapsedHours * 60
|
||||
);
|
||||
|
||||
const presenceEffect =
|
||||
(resolvedHeldEffects.find(
|
||||
(entry): entry is typeof entry & { effect: SetActorPresenceControlEffect } =>
|
||||
entry.effect.type === "setActorPresence"
|
||||
)?.effect ??
|
||||
findHeldSequenceControlEffect(heldSteps, "setActorPresence")) ??
|
||||
findHeldSequenceControlEffect(heldSteps, "setActorPresence") ??
|
||||
createSetActorPresenceControlEffect({
|
||||
target: createActorControlTargetRef(options.actorId),
|
||||
active: true
|
||||
});
|
||||
const animationEffect =
|
||||
resolvedHeldEffects.find(
|
||||
(entry): entry is typeof entry & { effect: PlayActorAnimationControlEffect } =>
|
||||
entry.effect.type === "playActorAnimation"
|
||||
)?.effect ?? findHeldSequenceControlEffect(heldSteps, "playActorAnimation");
|
||||
const pathEffectEntry = resolvedHeldEffects.find(
|
||||
(entry): entry is typeof entry & { effect: FollowActorPathControlEffect } =>
|
||||
entry.effect.type === "followActorPath"
|
||||
findHeldSequenceControlEffect(heldSteps, "playActorAnimation");
|
||||
const pathEffect = findHeldSequenceControlEffect(
|
||||
heldSteps,
|
||||
"followActorPath"
|
||||
);
|
||||
const elapsedHours = getProjectScheduleRoutineElapsedHoursAt(
|
||||
activeRoutine,
|
||||
options.dayNumber,
|
||||
options.timeOfDayHours
|
||||
);
|
||||
const pathEffect =
|
||||
pathEffectEntry?.effect ??
|
||||
findHeldSequenceControlEffect(heldSteps, "followActorPath");
|
||||
|
||||
return {
|
||||
actorId: options.actorId,
|
||||
@@ -483,10 +464,7 @@ export function resolveRuntimeActorScheduleState(options: {
|
||||
? null
|
||||
: resolveActorSchedulePathState({
|
||||
effect: pathEffect,
|
||||
elapsedHours:
|
||||
pathEffectEntry === undefined
|
||||
? elapsedHours
|
||||
: pathEffectEntry.elapsedMinutes / 60,
|
||||
elapsedHours,
|
||||
path: options.pathsById?.get(pathEffect.pathId) ?? null
|
||||
})
|
||||
};
|
||||
@@ -540,18 +518,9 @@ function resolveRuntimeScheduledControlRoutines(options: {
|
||||
continue;
|
||||
}
|
||||
|
||||
const elapsedHours = getProjectScheduleRoutineElapsedHoursAt(
|
||||
routine,
|
||||
options.dayNumber,
|
||||
options.timeOfDayHours
|
||||
);
|
||||
const effects = getProjectScheduleRoutineResolvedHeldControlEffectsAtMinute(
|
||||
routine,
|
||||
options.sequences,
|
||||
elapsedHours === null ? null : elapsedHours * 60
|
||||
).map((entry) => cloneControlEffect(entry.effect));
|
||||
|
||||
for (const effect of effects) {
|
||||
for (const effect of getHeldSequenceControlEffects(
|
||||
getProjectScheduleRoutineHeldSteps(routine, options.sequences)
|
||||
)) {
|
||||
const resolutionKey = getControlEffectResolutionKey(effect);
|
||||
|
||||
if (seenResolutionKeys.has(resolutionKey)) {
|
||||
|
||||
378
src/sequencer/project-sequence-steps.ts
Normal file
378
src/sequencer/project-sequence-steps.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
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 ToggleVisibilitySequenceEffect {
|
||||
stepClass: "impulse";
|
||||
type: "toggleVisibility";
|
||||
targetBrushId: string;
|
||||
visible?: boolean;
|
||||
}
|
||||
|
||||
type SequenceDefinitionLike = {
|
||||
id: string;
|
||||
effects: SequenceEffect[];
|
||||
};
|
||||
|
||||
type SequenceLibraryLike = {
|
||||
sequences: Record<string, SequenceDefinitionLike>;
|
||||
};
|
||||
|
||||
export type HeldSequenceStep = HeldControlSequenceEffect;
|
||||
|
||||
export type ImpulseSequenceStep =
|
||||
| ImpulseControlSequenceEffect
|
||||
| StartDialogueSequenceEffect
|
||||
| TeleportPlayerSequenceEffect
|
||||
| ToggleVisibilitySequenceEffect;
|
||||
|
||||
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 "toggleVisibility":
|
||||
return {
|
||||
stepClass: "impulse",
|
||||
type: "toggleVisibility",
|
||||
targetBrushId: effect.targetBrushId,
|
||||
visible: effect.visible
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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 `${effect.stepClass === "held" ? "Held" : "Impulse"}: ${getControlEffectLabel(effect.effect)}`;
|
||||
case "startDialogue":
|
||||
return "Impulse: Start Dialogue";
|
||||
case "teleportPlayer":
|
||||
return "Impulse: Teleport Player";
|
||||
case "toggleVisibility":
|
||||
return "Impulse: 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: "toggleVisibility",
|
||||
targetBrushId: link.action.targetBrushId,
|
||||
visible: link.action.visible
|
||||
}
|
||||
];
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
import { createOpaqueId } from "../core/ids";
|
||||
|
||||
import {
|
||||
cloneSequenceClip,
|
||||
DEFAULT_PROJECT_SEQUENCE_DURATION_MINUTES,
|
||||
getProjectSequenceDurationMinutes,
|
||||
cloneSequenceEffect,
|
||||
type SequenceClip,
|
||||
type SequenceEffect,
|
||||
type SequenceStep
|
||||
} from "./project-sequence-steps";
|
||||
|
||||
export interface ProjectSequence {
|
||||
id: string;
|
||||
title: string;
|
||||
durationMinutes: number;
|
||||
clips: SequenceClip[];
|
||||
effects: SequenceEffect[];
|
||||
}
|
||||
|
||||
export interface ProjectSequenceLibrary {
|
||||
@@ -29,16 +27,6 @@ function normalizeProjectSequenceTitle(title: string | undefined): string {
|
||||
return normalizedTitle;
|
||||
}
|
||||
|
||||
function normalizeProjectSequenceDurationMinutes(
|
||||
value: number | undefined
|
||||
): number {
|
||||
if (value === undefined || !Number.isFinite(value)) {
|
||||
return DEFAULT_PROJECT_SEQUENCE_DURATION_MINUTES;
|
||||
}
|
||||
|
||||
return Math.max(1, Math.trunc(value));
|
||||
}
|
||||
|
||||
export function createEmptyProjectSequenceLibrary(): ProjectSequenceLibrary {
|
||||
return {
|
||||
sequences: {}
|
||||
@@ -46,21 +34,18 @@ export function createEmptyProjectSequenceLibrary(): ProjectSequenceLibrary {
|
||||
}
|
||||
|
||||
export function createProjectSequence(
|
||||
overrides: Partial<Pick<ProjectSequence, "id" | "title" | "durationMinutes">> & {
|
||||
overrides: Partial<Pick<ProjectSequence, "id" | "title">> & {
|
||||
effects?: SequenceEffect[];
|
||||
clips?: SequenceClip[];
|
||||
steps?: SequenceStep[];
|
||||
} = {}
|
||||
): ProjectSequence {
|
||||
const clips = (overrides.clips ?? overrides.steps)?.map(cloneSequenceClip) ?? [];
|
||||
|
||||
return {
|
||||
id: overrides.id ?? createOpaqueId("sequence"),
|
||||
title: normalizeProjectSequenceTitle(overrides.title ?? "Sequence"),
|
||||
durationMinutes: normalizeProjectSequenceDurationMinutes(
|
||||
overrides.durationMinutes ??
|
||||
(clips.length > 0 ? getProjectSequenceDurationMinutes({ id: "sequence", clips }) : undefined)
|
||||
),
|
||||
clips
|
||||
effects: (overrides.effects ?? overrides.clips ?? overrides.steps)?.map(
|
||||
cloneSequenceEffect
|
||||
) ?? []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -70,8 +55,7 @@ export function cloneProjectSequence(
|
||||
return {
|
||||
id: sequence.id,
|
||||
title: sequence.title,
|
||||
durationMinutes: sequence.durationMinutes,
|
||||
clips: sequence.clips.map(cloneSequenceClip)
|
||||
effects: sequence.effects.map(cloneSequenceEffect)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,20 +79,19 @@ export function areProjectSequencesEqual(
|
||||
if (
|
||||
left.id !== right.id ||
|
||||
left.title !== right.title ||
|
||||
left.durationMinutes !== right.durationMinutes ||
|
||||
left.clips.length !== right.clips.length
|
||||
left.effects.length !== right.effects.length
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return left.clips.every((clip, index) => {
|
||||
const rightClip = right.clips[index];
|
||||
return left.effects.every((effect, index) => {
|
||||
const rightEffect = right.effects[index];
|
||||
|
||||
if (rightClip === undefined) {
|
||||
if (rightEffect === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return JSON.stringify(clip) === JSON.stringify(rightClip);
|
||||
return JSON.stringify(effect) === JSON.stringify(rightEffect);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user