Add type checks and default creation functions for cone, torus, and dialogue-related entities

This commit is contained in:
2026-04-15 09:06:03 +02:00
parent 90ed0812a4
commit cd259fb6de
2 changed files with 96 additions and 7 deletions

View File

@@ -821,6 +821,36 @@ export function isRadialPrismVertexId(
);
}
export function isConeFaceId(value: unknown): value is ConeFaceId {
return value === "bottom" || (typeof value === "string" && value.startsWith("side-"));
}
export function isConeEdgeId(value: unknown): value is ConeEdgeId {
return (
typeof value === "string" &&
(value.startsWith("bottom-") || value.startsWith("side-"))
);
}
export function isConeVertexId(value: unknown): value is ConeVertexId {
return value === "apex" || (typeof value === "string" && value.startsWith("bottom-"));
}
export function isTorusFaceId(value: unknown): value is TorusFaceId {
return typeof value === "string" && value.startsWith("face-");
}
export function isTorusEdgeId(value: unknown): value is TorusEdgeId {
return (
typeof value === "string" &&
(value.startsWith("major-") || value.startsWith("tube-"))
);
}
export function isTorusVertexId(value: unknown): value is TorusVertexId {
return typeof value === "string" && value.startsWith("vertex-");
}
export function isFaceUvRotationQuarterTurns(value: unknown): value is FaceUvRotationQuarterTurns {
return typeof value === "number" && FACE_UV_ROTATION_QUARTER_TURNS.includes(value as FaceUvRotationQuarterTurns);
}
@@ -898,11 +928,26 @@ export function createDefaultWedgeBrushFaces(): WedgeBrushFaces {
}
export function createDefaultRadialPrismBrushFaces(
sideCount = 12
sideCount = DEFAULT_RADIAL_PRISM_SIDE_COUNT
): Record<RadialPrismFaceId, BrushFace> {
return createDefaultBrushFaces(getRadialPrismFaceIds(sideCount));
}
export function createDefaultConeBrushFaces(
sideCount = DEFAULT_CONE_SIDE_COUNT
): Record<ConeFaceId, BrushFace> {
return createDefaultBrushFaces(getConeFaceIds(sideCount));
}
export function createDefaultTorusBrushFaces(
majorSegmentCount = DEFAULT_TORUS_MAJOR_SEGMENT_COUNT,
tubeSegmentCount = DEFAULT_TORUS_TUBE_SEGMENT_COUNT
): Record<TorusFaceId, BrushFace> {
return createDefaultBrushFaces(
getTorusFaceIds(majorSegmentCount, tubeSegmentCount)
);
}
export function createDefaultBoxBrushWaterSettings(): BoxBrushWaterSettings {
return {
colorHex: DEFAULT_BOX_BRUSH_WATER_SETTINGS.colorHex,

View File

@@ -1,5 +1,10 @@
import { createOpaqueId } from "../core/ids";
import type { Vec3 } from "../core/vector";
import {
areProjectDialoguesEqual,
cloneProjectDialogue,
type ProjectDialogue
} from "../dialogues/project-dialogues";
import { normalizeTimeOfDayHours } from "../document/project-time-settings";
import { isHexColorString } from "../document/world-settings";
@@ -74,7 +79,8 @@ export interface NpcEntity extends PositionedEntity {
presence: NpcPresence;
yawDegrees: number;
modelAssetId: string | null;
dialogueId: string | null;
dialogues: ProjectDialogue[];
defaultDialogueId: string | null;
collider: NpcColliderSettings;
}
@@ -1298,6 +1304,34 @@ function normalizeNpcDialogueId(
return normalizedDialogueId.length === 0 ? null : normalizedDialogueId;
}
function normalizeNpcDialogues(
dialogues: ProjectDialogue[] | undefined
): ProjectDialogue[] {
if (dialogues === undefined) {
return [];
}
return dialogues.map(cloneProjectDialogue);
}
function normalizeNpcDefaultDialogueId(
defaultDialogueId: string | null | undefined,
dialogues: readonly ProjectDialogue[],
legacyDialogueId?: string | null | undefined
): string | null {
const normalizedDefaultDialogueId = normalizeNpcDialogueId(
defaultDialogueId ?? legacyDialogueId ?? null
);
if (normalizedDefaultDialogueId === null) {
return null;
}
return dialogues.some((dialogue) => dialogue.id === normalizedDefaultDialogueId)
? normalizedDefaultDialogueId
: null;
}
export function normalizeInteractablePrompt(prompt: string): string {
const normalizedPrompt = prompt.trim();
@@ -1455,9 +1489,11 @@ export function createNpcEntity(
| "presence"
| "yawDegrees"
| "modelAssetId"
| "dialogueId"
| "dialogues"
| "defaultDialogueId"
>
> & {
dialogueId?: string | null;
collider?: Partial<NpcColliderSettings>;
} = {}
): NpcEntity {
@@ -1468,8 +1504,11 @@ export function createNpcEntity(
const modelAssetId = normalizeNpcModelAssetId(
overrides.modelAssetId ?? DEFAULT_NPC_MODEL_ASSET_ID
);
const dialogueId = normalizeNpcDialogueId(
overrides.dialogueId ?? DEFAULT_NPC_DIALOGUE_ID
const dialogues = normalizeNpcDialogues(overrides.dialogues);
const defaultDialogueId = normalizeNpcDefaultDialogueId(
overrides.defaultDialogueId ?? DEFAULT_NPC_DIALOGUE_ID,
dialogues,
overrides.dialogueId
);
const collider = createNpcColliderSettings(overrides.collider);
@@ -1490,7 +1529,8 @@ export function createNpcEntity(
presence,
yawDegrees: normalizeYawDegrees(yawDegrees),
modelAssetId,
dialogueId,
dialogues,
defaultDialogueId,
collider
};
}
@@ -1802,7 +1842,11 @@ export function areEntityInstancesEqual(left: EntityInstance, right: EntityInsta
areNpcPresencesEqual(left.presence, typedRight.presence) &&
left.yawDegrees === typedRight.yawDegrees &&
left.modelAssetId === typedRight.modelAssetId &&
left.dialogueId === typedRight.dialogueId &&
left.defaultDialogueId === typedRight.defaultDialogueId &&
left.dialogues.length === typedRight.dialogues.length &&
left.dialogues.every((dialogue, index) =>
areProjectDialoguesEqual(dialogue, typedRight.dialogues[index]!)
) &&
left.collider.mode === typedRight.collider.mode &&
left.collider.eyeHeight === typedRight.collider.eyeHeight &&
left.collider.capsuleRadius === typedRight.collider.capsuleRadius &&