178 lines
5.0 KiB
TypeScript
178 lines
5.0 KiB
TypeScript
import {
|
|
type AnyNode,
|
|
type AnyNodeId,
|
|
LevelNode,
|
|
type LevelNode as LevelNodeType,
|
|
resolveBuildingForLevel,
|
|
type StairNode,
|
|
} from '@pascal-app/core'
|
|
|
|
function sortLevelsByHeight(levels: LevelNodeType[]) {
|
|
return [...levels].sort((left, right) => left.level - right.level)
|
|
}
|
|
|
|
function isLevelNode(node: AnyNode | undefined): node is LevelNodeType {
|
|
return node?.type === 'level'
|
|
}
|
|
|
|
function getAllSceneLevels(nodes: Record<string, AnyNode>) {
|
|
return sortLevelsByHeight(
|
|
Object.values(nodes).filter((entry): entry is LevelNodeType => entry?.type === 'level'),
|
|
)
|
|
}
|
|
|
|
function getBuildingLevels(
|
|
nodes: Record<string, AnyNode>,
|
|
buildingId: AnyNodeId | string | null | undefined,
|
|
source?: LevelNodeType,
|
|
) {
|
|
if (!buildingId) return source ? [source] : []
|
|
const building = nodes[buildingId as AnyNodeId]
|
|
if (building?.type !== 'building') return source ? [source] : []
|
|
|
|
const levels = new Map<string, LevelNodeType>()
|
|
if (source) levels.set(source.id, source)
|
|
|
|
for (const childId of building.children ?? []) {
|
|
const child = nodes[childId as AnyNodeId]
|
|
if (isLevelNode(child)) levels.set(child.id, child)
|
|
}
|
|
|
|
for (const candidate of Object.values(nodes)) {
|
|
if (isLevelNode(candidate) && candidate.parentId === building.id) {
|
|
levels.set(candidate.id, candidate)
|
|
}
|
|
}
|
|
|
|
return sortLevelsByHeight(Array.from(levels.values()))
|
|
}
|
|
|
|
export function getBuildingLevelsForLevel(
|
|
nodes: Record<string, AnyNode>,
|
|
levelId: AnyNodeId | string | null | undefined,
|
|
) {
|
|
if (!levelId) return []
|
|
const source = nodes[levelId as AnyNodeId]
|
|
if (!isLevelNode(source)) return []
|
|
|
|
const buildingId = resolveBuildingForLevel(
|
|
source.id as AnyNodeId,
|
|
nodes as Record<AnyNodeId, AnyNode>,
|
|
)
|
|
return getBuildingLevels(nodes, buildingId, source)
|
|
}
|
|
|
|
export function getStairLevelOptions(nodes: Record<string, AnyNode>, stair: StairNode) {
|
|
for (const candidateId of [stair.fromLevelId, stair.parentId, stair.toLevelId]) {
|
|
if (isLevelNode(nodes[candidateId as AnyNodeId])) {
|
|
return getBuildingLevelsForLevel(nodes, candidateId)
|
|
}
|
|
}
|
|
|
|
return getAllSceneLevels(nodes)
|
|
}
|
|
|
|
export function resolveStairPlacementLevelId(
|
|
nodes: Record<string, AnyNode>,
|
|
preferredLevelId: AnyNodeId | string | null | undefined,
|
|
preferredBuildingId?: AnyNodeId | string | null,
|
|
) {
|
|
if (isLevelNode(nodes[preferredLevelId as AnyNodeId])) {
|
|
return preferredLevelId as LevelNodeType['id']
|
|
}
|
|
|
|
const buildingLevels = getBuildingLevels(nodes, preferredBuildingId)
|
|
return buildingLevels[0]?.id ?? getAllSceneLevels(nodes)[0]?.id ?? null
|
|
}
|
|
|
|
export function resolveStairFromLevelId(
|
|
nodes: Record<string, AnyNode>,
|
|
stair: StairNode,
|
|
levels = getStairLevelOptions(nodes, stair),
|
|
) {
|
|
const optionIds = new Set<string>(levels.map((level) => level.id))
|
|
if (stair.fromLevelId && optionIds.has(stair.fromLevelId)) return stair.fromLevelId
|
|
if (stair.parentId && optionIds.has(stair.parentId)) return stair.parentId
|
|
|
|
const toLevel = stair.toLevelId ? nodes[stair.toLevelId as AnyNodeId] : undefined
|
|
if (isLevelNode(toLevel)) {
|
|
const lowerLevel = [...levels].reverse().find((level) => level.level < toLevel.level)
|
|
if (lowerLevel) return lowerLevel.id
|
|
}
|
|
|
|
return levels[0]?.id ?? null
|
|
}
|
|
|
|
export function resolveStairToLevelId(
|
|
nodes: Record<string, AnyNode>,
|
|
stair: StairNode,
|
|
fromLevelId: AnyNodeId | string | null | undefined,
|
|
levels = getStairLevelOptions(nodes, stair),
|
|
) {
|
|
const optionIds = new Set<string>(levels.map((level) => level.id))
|
|
if (stair.toLevelId && stair.toLevelId !== fromLevelId && optionIds.has(stair.toLevelId)) {
|
|
return stair.toLevelId
|
|
}
|
|
|
|
const fromLevel = fromLevelId ? nodes[fromLevelId as AnyNodeId] : undefined
|
|
if (isLevelNode(fromLevel)) {
|
|
return levels.find((level) => level.level > fromLevel.level)?.id ?? fromLevel.id
|
|
}
|
|
|
|
return levels[0]?.id ?? null
|
|
}
|
|
|
|
export function resolveStairDestinationLevel({
|
|
createMissing,
|
|
fromLevelId,
|
|
nodes,
|
|
}: {
|
|
createMissing?: boolean
|
|
fromLevelId: AnyNodeId | string | null | undefined
|
|
nodes: Record<string, AnyNode>
|
|
}) {
|
|
if (!fromLevelId) return null
|
|
const fromLevel = nodes[fromLevelId as AnyNodeId]
|
|
if (!isLevelNode(fromLevel)) return null
|
|
|
|
const buildingId = resolveBuildingForLevel(
|
|
fromLevel.id as AnyNodeId,
|
|
nodes as Record<AnyNodeId, AnyNode>,
|
|
)
|
|
const levels = getBuildingLevelsForLevel(nodes, fromLevel.id)
|
|
const nextExistingLevel = levels.find((level) => level.level > fromLevel.level) ?? null
|
|
if (nextExistingLevel) {
|
|
return {
|
|
buildingId,
|
|
createdLevel: null,
|
|
fromLevel,
|
|
levels,
|
|
toLevel: nextExistingLevel,
|
|
}
|
|
}
|
|
|
|
if (createMissing && buildingId) {
|
|
const createdLevel = LevelNode.parse({
|
|
children: [],
|
|
level: fromLevel.level + 1,
|
|
parentId: buildingId,
|
|
})
|
|
|
|
return {
|
|
buildingId,
|
|
createdLevel,
|
|
fromLevel,
|
|
levels: sortLevelsByHeight([...levels, createdLevel]),
|
|
toLevel: createdLevel,
|
|
}
|
|
}
|
|
|
|
return {
|
|
buildingId,
|
|
createdLevel: null,
|
|
fromLevel,
|
|
levels,
|
|
toLevel: fromLevel,
|
|
}
|
|
}
|