diff --git a/public/data/contributors.json b/public/data/contributors.json index 26450bd..cfcfd29 100644 --- a/public/data/contributors.json +++ b/public/data/contributors.json @@ -5,7 +5,7 @@ "name": "SuprexDE", "profileId": "215af72b-24c5-63ab-f480-eec12ba2f4e5", "profileUrl": "https://outlasttrialsstats.com/profile/215af72b-24c5-63ab-f480-eec12ba2f4e5", - "maps": ["police-station"] + "maps": ["sleep-room"] } ] } diff --git a/public/data/elements.json b/public/data/elements.json index 80b7948..6c4d181 100644 --- a/public/data/elements.json +++ b/public/data/elements.json @@ -255,6 +255,32 @@ } } }, + { + "id": "shuttle", + "name": "Shuttle Gate", + "category": "door", + "color": "#465b92", + "render": { + "kind": "shuttle", + "length": 28, + "thickness": 7 + }, + "description": "Shuttle gate: a row of cells split by a control console.", + "propsSchema": { + "cells": { + "type": "number", + "label": "Cells" + }, + "enterable": { + "type": "boolean", + "label": "Enterable" + }, + "redButton": { + "type": "boolean", + "label": "Red button" + } + } + }, { "id": "objective", "name": "Objective", diff --git a/public/data/maps/index.json b/public/data/maps/index.json index b24d730..5a10c19 100644 --- a/public/data/maps/index.json +++ b/public/data/maps/index.json @@ -105,7 +105,7 @@ "name": "Sleep Room", "card": "https://outlasttrialsstats.com/game-assets/Poster_Program_Weekly.webp", "progress": 0, - "enabled": false + "enabled": true } ] } diff --git a/public/data/maps/sleep-room/map.json b/public/data/maps/sleep-room/map.json new file mode 100644 index 0000000..2d65daa --- /dev/null +++ b/public/data/maps/sleep-room/map.json @@ -0,0 +1,17 @@ +{ + "$schema": "../../../schemas/map.schema.json", + "id": "sleep-room", + "meta": { + "name": "Sleep Room", + "authors": [ + "SuprexDE" + ] + }, + "trials": [ + { + "id": "main", + "name": "Sleep Room", + "default": true + } + ] +} diff --git a/public/data/maps/sleep-room/trials/main.json b/public/data/maps/sleep-room/trials/main.json new file mode 100644 index 0000000..29a357f --- /dev/null +++ b/public/data/maps/sleep-room/trials/main.json @@ -0,0 +1,275 @@ +{ + "$schema": "../../../../schemas/trial.schema.json", + "mapId": "sleep-room", + "trialId": "main", + "floors": [ + { + "index": 0, + "name": "Base Floor" + } + ], + "filters": [], + "rooms": [ + { + "id": "room-0001", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 385, + 5 + ], + "rect": [ + 40, + 55 + ] + } + }, + { + "id": "room-0002", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 145, + 90 + ], + "rect": [ + 65, + 55 + ] + } + }, + { + "id": "room-0003", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 210, + 105 + ], + "rect": [ + 45, + 40 + ] + } + }, + { + "id": "room-0004", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 180, + 150 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0005", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 180, + 185 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0006", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 180, + 220 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0007", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 220, + 145 + ], + "rect": [ + 30, + 115 + ] + } + }, + { + "id": "room-0008", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 205, + 260 + ], + "rect": [ + 55, + 55 + ] + } + }, + { + "id": "room-0009", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 385, + 115 + ], + "rect": [ + 35, + 145 + ] + } + }, + { + "id": "room-0010", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 420, + 115 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0011", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 420, + 150 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0012", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 420, + 185 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0013", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 420, + 220 + ], + "rect": [ + 35, + 35 + ] + } + }, + { + "id": "room-0014", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 305, + 280 + ], + "rect": [ + 25, + 45 + ] + } + }, + { + "id": "room-0015", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 280, + 110 + ], + "path": "h65 v40 l5,15 l10,5 l25,5 v85 h-40 l-15,20 h-25 l-15,-20 h-40 v-115 h30 z" + } + }, + { + "id": "room-0016", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 390, + 60 + ], + "path": "h30 v55 h-35 v-20 h5 z" + } + }, + { + "id": "room-0017", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 390, + 260 + ], + "path": "h55 v5 h5 v35 h-5 v10 h-25 z" + } + }, + { + "id": "room-0018", + "floor": 0, + "zone": "standard", + "shape": { + "origin": [ + 290, + 325 + ], + "path": "h55 v25 h-5 v5 h-40 v-5 h-10 z" + } + } + ], + "placements": [], + "routes": [], + "shapes": [] +} diff --git a/public/schemas/elements.schema.json b/public/schemas/elements.schema.json index b99c230..d15a518 100644 --- a/public/schemas/elements.schema.json +++ b/public/schemas/elements.schema.json @@ -69,7 +69,8 @@ "crawl-passage", "obstacle", "spawn-room", - "stairs" + "stairs", + "shuttle" ] }, "length": { "type": "number", "exclusiveMinimum": 0 }, diff --git a/src/core/constants.ts b/src/core/constants.ts index 984dc0c..fbf6d83 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -165,6 +165,15 @@ export const CRAWL_BAR_SPACING = 1.6 export const STAIRS_RUNG_SPACING = 3 /** Spacing of the teeth on obstacles. */ export const OBSTACLE_TOOTH_SPACING = 2.5 +// Shuttle gate: ratios relative to the cell height (thickness = cell + 2 rails). +export const SHUTTLE_RAIL_RATIO = 0.2 +export const SHUTTLE_WALL_RATIO = 0.4 +export const SHUTTLE_CONSOLE_RATIO = 0.75 +/** Radius of the enterable dot; clamped to half the cell width. */ +export const SHUTTLE_DOT_RATIO = 0.4 +export const SHUTTLE_BUTTON_BOX_RATIO = 0.5 +export const SHUTTLE_BUTTON_RADIUS_RATIO = 0.1 +export const SHUTTLE_DEFAULT_CELLS = 4 // Number marker (dot → leader line → diamond badge with a number) export const MARKER_COLOR = '#aaaaaa' diff --git a/src/core/model/types.ts b/src/core/model/types.ts index 1ccecf2..2564e5a 100644 --- a/src/core/model/types.ts +++ b/src/core/model/types.ts @@ -299,6 +299,7 @@ export type StructuralKind = | 'obstacle' | 'spawn-room' | 'stairs' + | 'shuttle' export interface StructuralRender { kind: StructuralKind diff --git a/src/core/render/StructuralMarker.vue b/src/core/render/StructuralMarker.vue index c3b2c23..dd76898 100644 --- a/src/core/render/StructuralMarker.vue +++ b/src/core/render/StructuralMarker.vue @@ -5,6 +5,7 @@ import { ROOM_WALL_WIDTH, SELECTION_COLOR, SELECTION_RING_OFFSET, + SHUTTLE_DEFAULT_CELLS, } from '../constants' import { elementIconUrl } from '../model/dataSource' import type { ElementDefinition, Placement } from '../model/types' @@ -18,6 +19,11 @@ import { obstacleChevronsPath, obstacleTeethPath, placementTransform, + shuttleButtonBoxPath, + shuttleButtonPath, + shuttleDotsPath, + shuttleFramePath, + shuttleSeamsPath, spawnRoomFloorPath, spawnRoomWallPath, stairsArrowPath, @@ -54,6 +60,14 @@ const dims = computed(() => { const rectPath = computed(() => centeredRectPath(dims.value.length, dims.value.thickness)) const ascending = computed(() => props.placement.props?.direction !== 'down') +const shuttleCells = computed(() => { + const raw = Number(props.placement.props?.cells) + return Number.isFinite(raw) ? Math.max(1, Math.floor(raw)) : SHUTTLE_DEFAULT_CELLS +}) +/** Unchecked booleans are absent from `props`, so strict comparison is exact. */ +const shuttleEnterable = computed(() => props.placement.props?.enterable === true) +const shuttleRedButton = computed(() => props.placement.props?.redButton === true) + /** Door/window/stairs variants differ only by the library color, not by code. */ const bodyFill = computed(() => meta.value?.fill === 'element' ? props.element.color : undefined, @@ -118,6 +132,26 @@ const selectionBounds = computed(() => { +