diff --git a/backend/app/models/components/transform.py b/backend/app/models/components/transform.py index eb9b985..d764854 100644 --- a/backend/app/models/components/transform.py +++ b/backend/app/models/components/transform.py @@ -1,5 +1,5 @@ from .component import EntityComponent -from ..structs.spatial import Position2D, Size +from ..structs.spatial import Position2D, RectPosition class TransformComponent(EntityComponent): @@ -8,4 +8,4 @@ class TransformComponent(EntityComponent): """ position: Position2D - size: Size + size: RectPosition diff --git a/backend/app/services/rawdata/chk.py b/backend/app/services/rawdata/chk.py index 2f4de39..6815d28 100644 --- a/backend/app/services/rawdata/chk.py +++ b/backend/app/services/rawdata/chk.py @@ -366,11 +366,11 @@ def locations(self) -> list[chk_types.Location]: MRGN = struct.unpack( CHK_FORMATDICT["MRGN"], mrgn_bytes[i * format_size : (i + 1) * format_size] ) - if (MRGN[0], MRGN[1], MRGN[2], MRGN[3]) != (0, 0, 0, 0): + if (MRGN[0], MRGN[1], MRGN[2], MRGN[3]) != (0, 0, 0, 0) and i != 63: result.append( chk_types.Location( id=i, - string=self.strings[MRGN[4]], + string=self.strings[MRGN[4] - 1], position=spatial.Position(x=MRGN[0], y=MRGN[1]), size=spatial.Size(width=MRGN[2] - MRGN[0], height=MRGN[3] - MRGN[1]), elevation_flag=chk_types.ElevationFlag(MRGN[5]), @@ -1003,8 +1003,8 @@ def MRGN(self) -> bytes: "<4I2H", location.transform.position.x, location.transform.position.y, - location.transform.position.x + location.transform.size.width, - location.transform.position.y + location.transform.size.height, + location.transform.position.x + location.transform.size.right, + location.transform.position.y + location.transform.size.bottom, self.find_string_by_content(location.name).id, location.elevation_flags, ) diff --git a/backend/app/services/rawdata/converter.py b/backend/app/services/rawdata/converter.py index c6cb3b7..737ac2c 100644 --- a/backend/app/services/rawdata/converter.py +++ b/backend/app/services/rawdata/converter.py @@ -36,7 +36,12 @@ def tiles(self): tile_id=tile.id, transform=TransformComponent( position=Position2D(x=tile.position.x, y=tile.position.y), - size=Size(width=32, height=32), + size=RectPosition( + left=16, + top=16, + right=16, + bottom=16, + ), ), kind="Tile", ) @@ -53,7 +58,12 @@ def locations(self): name=location.string.content, transform=TransformComponent( position=Position2D(x=location.position.x, y=location.position.y), - size=Size(width=1, height=1), + size=RectPosition( + left=location.size.width // 2, + top=location.size.height // 2, + right=location.size.width // 2, + bottom=location.size.width // 2, + ), ), elevation_flags=location.elevation_flag, kind="Location", @@ -80,7 +90,12 @@ def mask(self): name=f"Mask {id}", transform=TransformComponent( position=Position2D(x=0, y=0), - size=Size(width=1, height=1), + size=RectPosition( + left=16, + top=16, + right=16, + bottom=16, + ), ), kind="Mask", flags=mask.flags, @@ -191,7 +206,7 @@ def placed_units(self): name=self.chk.unit_definitions[unit.unit_id].name, transform=TransformComponent( position=Position2D(x=unit.position.x, y=unit.position.y), - size=Size(width=1, height=1), + size=self.unit_definitions[unit.unit_id].size.bounds, ), kind="Unit", owner=self.players[unit.owner.id], @@ -218,7 +233,7 @@ def placed_sprites(self): name=self.dat.sprites[id].name, transform=TransformComponent( position=Position2D(x=sprite.position.x, y=sprite.position.y), - size=Size(width=1, height=1), + size=RectPosition(left=100, top=100, right=100, bottom=100), ), kind="Sprite", owner=self.players[sprite.owner.id], @@ -238,7 +253,7 @@ def default_unit_entities(self): name=unit_definition.name, transform=TransformComponent( position=Position2D(x=0, y=0), - size=Size(width=1, height=1), + size=unit_definition.size.bounds, ), kind="Unit", owner=self.players[0], @@ -264,7 +279,12 @@ def default_sprite_entities(self): name=sprite.name, transform=TransformComponent( position=Position2D(x=0, y=0), - size=Size(width=1, height=1), + size=RectPosition( + left=100, + top=100, + bottom=100, + right=100, + ), ), kind="Sprite", owner=self.players[0], diff --git a/frontend/components/core/usemap-editor.tsx b/frontend/components/core/usemap-editor.tsx index c6a3af8..8402ff2 100644 --- a/frontend/components/core/usemap-editor.tsx +++ b/frontend/components/core/usemap-editor.tsx @@ -55,12 +55,18 @@ function UsemapEditorMenu({ path: string[]; handleChange: (path: string[], value: any) => void; }) { - const onChange = (e: ChangeEvent) => { + const onChangeString = (e: ChangeEvent) => { const timeout = setTimeout(() => { handleChange(path, e.target.value); }, 500); return () => clearTimeout(timeout); }; + const onChangeNumber = (e: ChangeEvent) => { + const timeout = setTimeout(() => { + handleChange(path, Number(e.target.value)); + }, 500); + return () => clearTimeout(timeout); + }; const fixedLabel = label .replace(/_/g, " ") @@ -140,13 +146,17 @@ function UsemapEditorMenu({ ); break; case "number": input = ( - + ); break; case "string": { @@ -154,7 +164,7 @@ function UsemapEditorMenu({ ); break; diff --git a/frontend/components/layout/viewport.tsx b/frontend/components/layout/viewport.tsx index 5ef8635..dc938da 100644 --- a/frontend/components/layout/viewport.tsx +++ b/frontend/components/layout/viewport.tsx @@ -1,12 +1,15 @@ "use client"; -import { useCallback, useRef } from "react"; +import React, { useCallback, useRef } from "react"; import { useEntireCanvas } from "@/hooks/useImage"; import { TILE_SIZE } from "@/lib/scterrain"; import { Viewport } from "@/types/viewport"; import { useDragViewport } from "@/hooks/useDragViewport"; import { useElementResize } from "@/hooks/useElementResize"; import { useDroppableContext } from "@/hooks/useDraggableAsset"; +import { findEntityAtPosition } from "@/lib/entityUtils"; +import { useEntityStore } from "@/store/entityStore"; +import { useUsemapStore } from "../pages/editor-page"; export const MapImage = ({ className }: { className?: string }) => { const viewportCanvasRef = useRef(null); @@ -40,6 +43,9 @@ export const MapImage = ({ className }: { className?: string }) => { viewCanvas.width = canvasWidth; viewCanvas.height = canvasHeight; + // 캔버스 완전히 지우기 + viewCtx.clearRect(0, 0, canvasWidth, canvasHeight); + viewCtx.drawImage( image, v.startX * TILE_SIZE, @@ -53,12 +59,71 @@ export const MapImage = ({ className }: { className?: string }) => { ); }, [image]); + const usemap = useUsemapStore((state) => state.usemap); + const setEntity = useEntityStore((state) => state.setEntity); + const selectedEntity = useEntityStore((state) => state.entity); + const deleteEntity = useUsemapStore((state) => state.deleteEntity); + + const handleCanvasClick = useCallback( + (event: React.MouseEvent) => { + if (!usemap) return; + + // Canvas 요소의 bounding rect 가져오기 + const canvasRect = viewportCanvasRef.current!.getBoundingClientRect(); + + // Canvas 내에서의 상대 좌표 + const relativeX = event.clientX - canvasRect.left; + const relativeY = event.clientY - canvasRect.top; + + // Canvas 스케일 팩터 계산 (실제 크기 vs CSS 크기) + const scaleX = + viewportCanvasRef.current!.width / + viewportCanvasRef.current!.clientWidth; + const scaleY = + viewportCanvasRef.current!.height / + viewportCanvasRef.current!.clientHeight; + + // 스케일 팩터를 고려한 실제 캔버스 좌표 + const scaledX = relativeX * scaleX; + const scaledY = relativeY * scaleY; + + // Viewport offset을 고려한 실제 맵 좌표 + const mapX = scaledX + viewportRef.current.startX * TILE_SIZE; + const mapY = scaledY + viewportRef.current.startY * TILE_SIZE; + + const units = usemap.entities.filter((e) => e.data?.kind === "Unit"); + + const clickedEntity = findEntityAtPosition(mapX, mapY, units); + + if (clickedEntity) { + setEntity(clickedEntity); + } + }, + [usemap], + ); + + const handleDelete = () => { + console.log("yay"); + if (selectedEntity) { + deleteEntity(selectedEntity); + } + }; + const handleKeydown = (e: React.KeyboardEvent) => { + switch (e.key) { + case "Delete": { + console.log("ya"); + handleDelete(); + } + } + }; + /** * Viewport dragging handling hook. */ const { onMouseMove, onMouseUp, onMousedown, isDragging } = useDragViewport( viewportRef, paint, + handleCanvasClick, // 클릭 핸들러 전달 ); const { setNodeRef } = useDroppableContext({ @@ -79,13 +144,15 @@ export const MapImage = ({ className }: { className?: string }) => { ); diff --git a/frontend/hooks/useDragViewport.ts b/frontend/hooks/useDragViewport.ts index 363e013..ff0301f 100644 --- a/frontend/hooks/useDragViewport.ts +++ b/frontend/hooks/useDragViewport.ts @@ -6,17 +6,21 @@ import { Viewport } from "@/types/viewport"; * Hook for handle dragging viewport. * @param vpRef Viewport Ref * @param onViewportChange Event handler for viewport changed. + * @param onCanvasClick Click handler for canvas clicks (when not dragging) * @returns */ export function useDragViewport( vpRef: React.MutableRefObject, onViewportChange: () => void, + onCanvasClick?: (e: React.MouseEvent) => void, ) { const isDragging = useRef(false); const dragStart = useRef<{ x: number; y: number } | null>(); + const hasDragged = useRef(false); const onMousedown = (e: React.MouseEvent) => { isDragging.current = true; + hasDragged.current = false; dragStart.current = { x: e.clientX, y: e.clientY }; }; @@ -31,6 +35,7 @@ export function useDragViewport( const deltaY = Math.round(dy / TILE_SIZE); if (deltaX === 0 && deltaY === 0) return; + hasDragged.current = true; // 드래그 했음을 기록 vpRef.current.startX = Math.max(0, vpRef.current.startX - deltaX); vpRef.current.startY = Math.max(0, vpRef.current.startY - deltaY); dragStart.current = { x: e.clientX, y: e.clientY }; @@ -43,9 +48,18 @@ export function useDragViewport( } }; - function onMouseUp() { + function onMouseUp(e: React.MouseEvent) { + const wasDragging = isDragging.current; + const didDrag = hasDragged.current; + isDragging.current = false; dragStart.current = null; + hasDragged.current = false; + + // 드래그하지 않고 단순 클릭한 경우 + if (wasDragging && !didDrag && onCanvasClick) { + onCanvasClick(e); + } } return { onMousedown, onMouseMove, onMouseUp, isDragging }; diff --git a/frontend/hooks/useImage.ts b/frontend/hooks/useImage.ts index 1089eea..1a6a0bf 100644 --- a/frontend/hooks/useImage.ts +++ b/frontend/hooks/useImage.ts @@ -17,11 +17,13 @@ import { getLocationImage, getPlacedSpriteImages, getPlacedUnitImage, + getSelectionHighlightImage, } from "@/lib/scimage"; import { Unit } from "@/types/schemas/entities/Unit"; import { Sprite } from "@/types/schemas/entities/Sprite"; import { Tile } from "@/types/schemas/entities/Tile"; import { Location } from "@/types/schemas/entities/Location"; +import { useEntityStore } from "@/store/entityStore"; const safeGet = async (promise: Promise<{ data: T }>) => { try { @@ -134,17 +136,21 @@ interface ViewportImageBundle { unit?: ImageBitmap; location?: ImageBitmap; sprite?: ImageBitmap; + selectionHighlight?: ImageBitmap; } export function useViewportImage(): ViewportImageBundle { const usemap = useUsemapStore((store) => store.usemap); const tileGroup = useTileGroup(); const tilesetData = useTilesetData(); + const selectedEntity = useEntityStore((state) => state.entity); const terrainImage = useRef(); const [unitImage, setUnitImage] = useState(); const [spriteImage, setSpriteImage] = useState(); const locationImage = useRef(); + const [selectionHighlightImage, setSelectionHighlightImage] = + useState(); /** Create terrain image */ useEffect(() => { @@ -212,6 +218,7 @@ export function useViewportImage(): ViewportImageBundle { })(); }, [usemap?.entities, loading]); + /** Create Location Image */ useEffect(() => { if (!usemap) return; locationImage.current = getLocationImage( @@ -222,17 +229,31 @@ export function useViewportImage(): ViewportImageBundle { ); }, [usemap?.entities]); + /** Create Selection Highlight Image */ + useEffect(() => { + if (!usemap) { + setSelectionHighlightImage(undefined); + return; + } + console.log("yay"); + setSelectionHighlightImage( + getSelectionHighlightImage(usemap.terrain, selectedEntity || undefined), + ); + }, [selectedEntity]); + return { terrain: terrainImage.current, unit: unitImage, sprite: spriteImage, location: locationImage.current, + selectionHighlight: selectionHighlightImage, }; } export function useEntireCanvas() { const usemap = useUsemapStore((state) => state.usemap); - const { terrain, unit, sprite, location } = useViewportImage(); + const { terrain, unit, sprite, location, selectionHighlight } = + useViewportImage(); const [bitmap, setBitmap] = useState(); @@ -244,11 +265,16 @@ export function useEntireCanvas() { const canvas = new OffscreenCanvas(w, h); const ctx = canvas.getContext("2d")!; + // 캔버스 완전히 지우기 ctx.clearRect(0, 0, w, h); + if (terrain) ctx.drawImage(terrain, 0, 0); if (unit) ctx.drawImage(unit, 0, 0); if (sprite) ctx.drawImage(sprite, 0, 0); if (location) ctx.drawImage(location, 0, 0); + if (selectionHighlight) ctx.drawImage(selectionHighlight, 0, 0); + + console.log("yo", selectionHighlight); setBitmap(canvas.transferToImageBitmap()); }, [ @@ -257,6 +283,7 @@ export function useEntireCanvas() { unit, sprite, location, + selectionHighlight, ]); return { image: bitmap }; diff --git a/frontend/lib/entityUtils.ts b/frontend/lib/entityUtils.ts new file mode 100644 index 0000000..01a4cf1 --- /dev/null +++ b/frontend/lib/entityUtils.ts @@ -0,0 +1,21 @@ +import { Asset } from "@/types/asset"; +import { Entity } from "@/types/schemas/entities/Entity"; + +export function findEntityAtPosition( + x: number, + y: number, + entities: Asset[], +): Asset | null { + const entitiesAtPosition = entities.filter((entity) => { + if (!entity.data) return; + const transform = entity.data!.transform; + return ( + x >= transform.position.x - transform.size.left && + x < transform.position.x + transform.size.right && + y >= transform.position.y - transform.size.top && + y < transform.position.y + transform.size.bottom + ); + }); + + return entitiesAtPosition[entitiesAtPosition.length - 1] || null; +} diff --git a/frontend/lib/scimage.ts b/frontend/lib/scimage.ts index f1a790c..761c863 100644 --- a/frontend/lib/scimage.ts +++ b/frontend/lib/scimage.ts @@ -4,6 +4,8 @@ import { Sprite } from "@/types/schemas/entities/Sprite"; import { FrameMeta, SCImageBundle } from "@/types/SCImage"; import { Location } from "@/types/schemas/entities/Location"; import { TILE_SIZE } from "./scterrain"; +import { Asset } from "@/types/asset"; +import { Entity } from "@/types/schemas/entities/Entity"; type props = { image: Blob; frame: number; meta: FrameMeta }; export async function fetchFrameImage({ image, frame, meta }: props) { @@ -11,8 +13,25 @@ export async function fetchFrameImage({ image, frame, meta }: props) { if (!rect) throw new Error(`frame ${frame} not found.`); const bitmap = await createImageBitmap(image); + const frameImage = await createImageBitmap( + bitmap, + rect.x, + rect.y, + rect.width, + rect.height, + ); - return createImageBitmap(bitmap, rect.x, rect.y, rect.width, rect.height); + // x_offset, y_offset을 고려해서 중심 정렬된 이미지 생성 + const canvas = new OffscreenCanvas( + rect.width + Math.abs(rect.x_offset) * 2, + rect.height + Math.abs(rect.y_offset) * 2, + ); + const ctx = canvas.getContext("2d")!; + + // 캔버스 중앙에 이미지를 그려서 offset 효과 적용 + ctx.drawImage(frameImage, Math.abs(rect.x_offset), Math.abs(rect.y_offset)); + + return canvas.transferToImageBitmap(); } export function createTeamColorUnitImage( @@ -96,18 +115,19 @@ export async function getPlacedUnitImage( ctx.drawImage( colored, - unit.transform!.position.x, - unit.transform!.position.y, + unit.transform!.position.x - colored.width / 2, + unit.transform!.position.y - colored.height / 2, ); } else { + const unitImage = await fetchFrameImage({ + image: image.diffuse, + frame: 0, + meta: image.meta, + }); ctx.drawImage( - await fetchFrameImage({ - image: image.diffuse, - frame: 0, - meta: image.meta, - }), - unit.transform!.position.x, - unit.transform!.position.y, + unitImage, + unit.transform!.position.x - unitImage.width / 2, + unit.transform!.position.y - unitImage.height / 2, ); } }); @@ -133,14 +153,15 @@ export async function getPlacedSpriteImages( const image = SCImages.get(imageID); if (!image) return; + const spriteFrameImage = await fetchFrameImage({ + image: image.diffuse, + frame: 0, + meta: image.meta, + }); ctx.drawImage( - await fetchFrameImage({ - image: image.diffuse, - frame: 0, - meta: image.meta, - }), - sprite.transform!.position.x, - sprite.transform!.position.y, + spriteFrameImage, + sprite.transform!.position.x - spriteFrameImage.width / 2, + sprite.transform!.position.y - spriteFrameImage.height / 2, ); }); @@ -159,8 +180,10 @@ export function getLocationImage(terrain: RawTerrain, locations: Location[]) { locations.forEach((location) => { if (location.id != 63) { - const width = location.transform.size.width; - const height = location.transform.size.height; + const width = + location.transform.size.left + location.transform.size.right; + const height = + location.transform.size.top + location.transform.size.bottom; ctx.fillStyle = "rgba(0, 0, 255, 0.15)"; ctx.fillRect( location.transform.position.x, @@ -187,3 +210,35 @@ export function getLocationImage(terrain: RawTerrain, locations: Location[]) { return canvas.transferToImageBitmap(); } + +export function getSelectionHighlightImage( + terrain: RawTerrain, + selectedEntity?: Asset, +) { + const canvas = new OffscreenCanvas( + terrain.size.width * TILE_SIZE, + terrain.size.height * TILE_SIZE, + ); + const ctx = canvas.getContext("2d")!; + + if (!selectedEntity || !selectedEntity.data) { + ctx.clearRect( + 0, + 0, + terrain.size.width * TILE_SIZE, + terrain.size.height * TILE_SIZE, + ); + return canvas.transferToImageBitmap(); + } + const transform = selectedEntity.data?.transform; + ctx.strokeStyle = "rgb(0, 255, 0)"; + ctx.lineWidth = 2; + const x = transform.position.x - transform.size.left; + const y = transform.position.y - transform.size.top; + const width = transform.size.left + transform.size.right; + const height = transform.size.top + transform.size.bottom; + + ctx.strokeRect(x, y, width, height); + + return canvas.transferToImageBitmap(); +} diff --git a/frontend/store/entityStore.ts b/frontend/store/entityStore.ts index 71a5bd9..331115f 100644 --- a/frontend/store/entityStore.ts +++ b/frontend/store/entityStore.ts @@ -1,9 +1,10 @@ -import { AssetNode } from "@/types/asset"; +import { Asset, AssetNode } from "@/types/asset"; +import { Entity } from "@/types/schemas/entities/Entity"; import { create } from "zustand"; interface EntityStore { - entity: AssetNode | null; - setEntity: (entity: AssetNode) => void; + entity: Asset | null; + setEntity: (entity: Asset | null) => void; checkedEntities: AssetNode[]; setCheckedEntities: (entities: AssetNode[]) => void; } @@ -12,7 +13,8 @@ export const useEntityStore = create((set) => ({ entity: null, checkedEntities: [], setEntity: (entity) => { - set({ entity }); + if (entity) set({ entity }); + else set({ entity: null }); }, setCheckedEntities: (entities) => { set({ checkedEntities: entities }); diff --git a/frontend/store/mapStore.ts b/frontend/store/mapStore.ts index e46c4f3..d450ee8 100644 --- a/frontend/store/mapStore.ts +++ b/frontend/store/mapStore.ts @@ -7,6 +7,7 @@ import { produce } from "immer"; import { Entity } from "@/types/schemas/entities/Entity"; import { Asset } from "@/types/asset"; import api from "@/lib/api"; +import { useEntityStore } from "./entityStore"; // export const useUsemapStore = create((set, get) => ({ // usemap: null, @@ -106,7 +107,13 @@ export const createUsemapStore = () => { draft.entities.push(entity); }), })), - deleteEntity: (entity: Asset) => + deleteEntity: (entity: Asset) => { + // 선택된 엔티티가 삭제되는 엔티티와 같으면 선택 해제 + const currentSelected = useEntityStore.getState().entity; + if (currentSelected && currentSelected.id === entity.id) { + useEntityStore.getState().setEntity(null); + } + set((state) => ({ usemap: produce(state.usemap, (draft: Usemap) => { draft.entities = draft.entities.filter( @@ -114,7 +121,8 @@ export const createUsemapStore = () => { ); // maybe can replaced with delete draft.entities[entity.id]? }), - })), + })); + }, updateEntityAssetName: (id: number, name: string) => set((state) => ({ usemap: produce(state.usemap, (draft: Usemap) => { diff --git a/frontend/types/schemas/components/RectPosition.ts b/frontend/types/schemas/components/RectPosition.ts new file mode 100644 index 0000000..1fd5e83 --- /dev/null +++ b/frontend/types/schemas/components/RectPosition.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const RectPositionSchema = z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), +}); +export type RectPosition = z.infer; diff --git a/frontend/types/schemas/components/TransformComponent.ts b/frontend/types/schemas/components/TransformComponent.ts index b4b0299..b958591 100644 --- a/frontend/types/schemas/components/TransformComponent.ts +++ b/frontend/types/schemas/components/TransformComponent.ts @@ -8,7 +8,12 @@ export const TransformComponentSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."); export type TransformComponent = z.infer; diff --git a/frontend/types/schemas/entities/Entity.ts b/frontend/types/schemas/entities/Entity.ts index 156acf0..0aaa5a1 100644 --- a/frontend/types/schemas/entities/Entity.ts +++ b/frontend/types/schemas/entities/Entity.ts @@ -12,7 +12,12 @@ export const EntitySchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/entities/Location.ts b/frontend/types/schemas/entities/Location.ts index 0017eea..05e4182 100644 --- a/frontend/types/schemas/entities/Location.ts +++ b/frontend/types/schemas/entities/Location.ts @@ -11,7 +11,12 @@ export const LocationSchema = z.object({ x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/entities/Mask.ts b/frontend/types/schemas/entities/Mask.ts index ea9dde1..114dca5 100644 --- a/frontend/types/schemas/entities/Mask.ts +++ b/frontend/types/schemas/entities/Mask.ts @@ -12,7 +12,12 @@ export const MaskSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/entities/Sprite.ts b/frontend/types/schemas/entities/Sprite.ts index f8da4ad..3ad6271 100644 --- a/frontend/types/schemas/entities/Sprite.ts +++ b/frontend/types/schemas/entities/Sprite.ts @@ -12,7 +12,12 @@ export const SpriteSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z diff --git a/frontend/types/schemas/entities/Tile.ts b/frontend/types/schemas/entities/Tile.ts index be9acc7..c2f854a 100644 --- a/frontend/types/schemas/entities/Tile.ts +++ b/frontend/types/schemas/entities/Tile.ts @@ -11,7 +11,12 @@ export const TileSchema = z.object({ x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/entities/TransformComponent.ts b/frontend/types/schemas/entities/TransformComponent.ts index b4b0299..b958591 100644 --- a/frontend/types/schemas/entities/TransformComponent.ts +++ b/frontend/types/schemas/entities/TransformComponent.ts @@ -8,7 +8,12 @@ export const TransformComponentSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."); export type TransformComponent = z.infer; diff --git a/frontend/types/schemas/entities/Unit.ts b/frontend/types/schemas/entities/Unit.ts index 3345527..4d6285a 100644 --- a/frontend/types/schemas/entities/Unit.ts +++ b/frontend/types/schemas/entities/Unit.ts @@ -12,7 +12,12 @@ export const UnitSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z diff --git a/frontend/types/schemas/project/Entity.ts b/frontend/types/schemas/project/Entity.ts index 156acf0..0aaa5a1 100644 --- a/frontend/types/schemas/project/Entity.ts +++ b/frontend/types/schemas/project/Entity.ts @@ -12,7 +12,12 @@ export const EntitySchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/project/FlingyDefinition.ts b/frontend/types/schemas/project/FlingyDefinition.ts new file mode 100644 index 0000000..94c4fc2 --- /dev/null +++ b/frontend/types/schemas/project/FlingyDefinition.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; + +export const FlingyDefinitionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + sprite: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([z.number().int(), z.null()]), + selection_circle_offset: z.union([z.number().int(), z.null()]), + }), + top_speed: z.number().int(), + acceleration: z.number().int(), + halt_distance: z.number().int(), + turn_radius: z.number().int(), + unused: z.number().int(), + move_control: z.number().int(), +}); +export type FlingyDefinition = z.infer; diff --git a/frontend/types/schemas/project/ImageDefinition.ts b/frontend/types/schemas/project/ImageDefinition.ts new file mode 100644 index 0000000..763cd43 --- /dev/null +++ b/frontend/types/schemas/project/ImageDefinition.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +export const ImageDefinitionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), +}); +export type ImageDefinition = z.infer; diff --git a/frontend/types/schemas/project/Location.ts b/frontend/types/schemas/project/Location.ts index 0017eea..05e4182 100644 --- a/frontend/types/schemas/project/Location.ts +++ b/frontend/types/schemas/project/Location.ts @@ -11,7 +11,12 @@ export const LocationSchema = z.object({ x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/project/Mask.ts b/frontend/types/schemas/project/Mask.ts index ea9dde1..114dca5 100644 --- a/frontend/types/schemas/project/Mask.ts +++ b/frontend/types/schemas/project/Mask.ts @@ -12,7 +12,12 @@ export const MaskSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/project/OrderDefinition.ts b/frontend/types/schemas/project/OrderDefinition.ts new file mode 100644 index 0000000..49c324e --- /dev/null +++ b/frontend/types/schemas/project/OrderDefinition.ts @@ -0,0 +1,17 @@ +import { z } from "zod"; + +export const OrderDefinitionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + label: z.number().int(), + use_weapon_targeting: z.boolean(), + can_be_interrupted: z.boolean(), + can_be_queued: z.boolean(), + targeting: z.number().int(), + energy: z.number().int(), + animation: z.number().int(), + highlight: z.number().int(), + obscured_order: z.number().int(), +}); +export type OrderDefinition = z.infer; diff --git a/frontend/types/schemas/project/PortraitDefinition.ts b/frontend/types/schemas/project/PortraitDefinition.ts new file mode 100644 index 0000000..8193252 --- /dev/null +++ b/frontend/types/schemas/project/PortraitDefinition.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const PortraitDefinitionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + portrait_file: z.number().int(), + smk_change: z.number().int(), + unknown1: z.number().int(), +}); +export type PortraitDefinition = z.infer; diff --git a/frontend/types/schemas/project/Sprite.ts b/frontend/types/schemas/project/Sprite.ts index f8da4ad..3ad6271 100644 --- a/frontend/types/schemas/project/Sprite.ts +++ b/frontend/types/schemas/project/Sprite.ts @@ -12,7 +12,12 @@ export const SpriteSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z diff --git a/frontend/types/schemas/project/SpriteDefinition.ts b/frontend/types/schemas/project/SpriteDefinition.ts new file mode 100644 index 0000000..b1596e4 --- /dev/null +++ b/frontend/types/schemas/project/SpriteDefinition.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; + +export const SpriteDefinitionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([z.number().int(), z.null()]), + selection_circle_offset: z.union([z.number().int(), z.null()]), +}); +export type SpriteDefinition = z.infer; diff --git a/frontend/types/schemas/project/TechRestriction.ts b/frontend/types/schemas/project/TechRestriction.ts new file mode 100644 index 0000000..210cb35 --- /dev/null +++ b/frontend/types/schemas/project/TechRestriction.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const TechRestrictionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + player_availability: z.array(z.boolean()).min(12).max(12), + player_already_researched: z.array(z.boolean()).min(12).max(12), + default_availability: z.boolean(), + default_already_researched: z.boolean(), + uses_default: z.array(z.boolean()).min(12).max(12), +}); +export type TechRestriction = z.infer; diff --git a/frontend/types/schemas/project/Technology.ts b/frontend/types/schemas/project/Technology.ts new file mode 100644 index 0000000..9dd826b --- /dev/null +++ b/frontend/types/schemas/project/Technology.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const TechnologySchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean(), + cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + energy: z.number().int().gte(0).default(0), + }), + energy_required: z.boolean(), + icon: z.number().int(), + label: z.number().int(), + race: z.number().int(), +}); +export type Technology = z.infer; diff --git a/frontend/types/schemas/project/Tile.ts b/frontend/types/schemas/project/Tile.ts index be9acc7..c2f854a 100644 --- a/frontend/types/schemas/project/Tile.ts +++ b/frontend/types/schemas/project/Tile.ts @@ -11,7 +11,12 @@ export const TileSchema = z.object({ x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z.enum(["Unit", "Sprite", "Location", "Tile", "Mask"]), diff --git a/frontend/types/schemas/project/Unit.ts b/frontend/types/schemas/project/Unit.ts index 3345527..4d6285a 100644 --- a/frontend/types/schemas/project/Unit.ts +++ b/frontend/types/schemas/project/Unit.ts @@ -12,7 +12,12 @@ export const UnitSchema = z x: z.number().int().default(0), y: z.number().int().default(0), }), - size: z.object({ height: z.number().int(), width: z.number().int() }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), }) .describe("An entity component can have spatial data."), kind: z diff --git a/frontend/types/schemas/project/UnitDefinition.ts b/frontend/types/schemas/project/UnitDefinition.ts new file mode 100644 index 0000000..12ce9e2 --- /dev/null +++ b/frontend/types/schemas/project/UnitDefinition.ts @@ -0,0 +1,214 @@ +import { z } from "zod"; + +export const UnitDefinitionSchema = z + .object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean().default(true), + specification: z.object({ + name: z.string().default("Unit Specification"), + graphics: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + sprite: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([z.number().int(), z.null()]), + selection_circle_offset: z.union([z.number().int(), z.null()]), + }), + top_speed: z.number().int(), + acceleration: z.number().int(), + halt_distance: z.number().int(), + turn_radius: z.number().int(), + unused: z.number().int(), + move_control: z.number().int(), + }), + subunit1: z.number().int(), + subunit2: z.number().int(), + infestation: z.union([z.number().int(), z.null()]), + construction_animation: z.number().int(), + unit_direction: z.number().int(), + portrait: z.number().int(), + label: z.number().int(), + }), + stats: z.object({ + name: z.string().default("Unit Status"), + hit_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + shield_enable: z.boolean(), + shield_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + energy_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + armor_points: z.number().int().lt(256).default(0), + armor_upgrade: z.number().int(), + rank: z.number().int(), + elevation_level: z.number().int(), + }), + weapons: z.object({ + ground_weapon: z.union([ + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.null(), + ]), + max_ground_hits: z.number().int(), + air_weapon: z.union([ + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.null(), + ]), + max_air_hits: z.number().int(), + target_acquisition_range: z.number().int(), + sight_range: z.number().int(), + special_ability_flags: z.number().int(), + }), + ai: z.object({ + name: z.string().default("Unit AI"), + computer_idle: z.number().int(), + human_idle: z.number().int(), + return_to_idle: z.number().int(), + attack_unit: z.number().int(), + attack_and_move: z.number().int(), + internal: z.number().int(), + right_click: z.number().int(), + }), + sound: z.object({ + name: z.string().default("Unit Sound"), + ready: z.union([z.number().int(), z.null()]), + what_start: z.number().int(), + what_end: z.number().int(), + piss_start: z.union([z.number().int(), z.null()]), + piss_end: z.union([z.number().int(), z.null()]), + yes_start: z.union([z.number().int(), z.null()]), + yes_end: z.union([z.number().int(), z.null()]), + }), + size: z.object({ + name: z.string().default("Unit Size"), + size_type: z.number().int(), + placement_box_size: z.object({ + height: z.number().int(), + width: z.number().int(), + }), + bounds: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), + addon_position: z.union([ + z.object({ + x: z.number().int().default(0), + y: z.number().int().default(0), + }), + z.null(), + ]), + }), + cost: z.object({ + name: z.string().default("Unit Cost"), + cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + build_score: z.number().int(), + destroy_score: z.number().int(), + is_broodwar: z.boolean(), + supply: z.object({ + required: z.number().int(), + provided: z.number().int(), + }), + space: z.object({ + required: z.number().int(), + provided: z.number().int(), + }), + }), + }) + .describe("Definition of unit specification."); +export type UnitDefinition = z.infer; diff --git a/frontend/types/schemas/project/UnitRestriction.ts b/frontend/types/schemas/project/UnitRestriction.ts new file mode 100644 index 0000000..d1f4d0b --- /dev/null +++ b/frontend/types/schemas/project/UnitRestriction.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const UnitRestrictionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + availability: z.array(z.boolean()), + global_availability: z.boolean(), + uses_defaults: z.array(z.boolean()), +}); +export type UnitRestriction = z.infer; diff --git a/frontend/types/schemas/project/Upgrade.ts b/frontend/types/schemas/project/Upgrade.ts new file mode 100644 index 0000000..f0e68fc --- /dev/null +++ b/frontend/types/schemas/project/Upgrade.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +export const UpgradeSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean(), + base_cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + factor_cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + icon: z.number().int(), + label: z.number().int(), + race: z.number().int(), +}); +export type Upgrade = z.infer; diff --git a/frontend/types/schemas/project/UpgradeRestriction.ts b/frontend/types/schemas/project/UpgradeRestriction.ts new file mode 100644 index 0000000..8ecb85c --- /dev/null +++ b/frontend/types/schemas/project/UpgradeRestriction.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; + +export const UpgradeRestrictionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + player_maximum_level: z.array(z.number().int()).min(12).max(12), + player_minimum_level: z.array(z.number().int()).min(12).max(12), + default_maximum_level: z.number().int(), + default_minimum_level: z.number().int(), + uses_default: z.array(z.boolean()).min(12).max(12), +}); +export type UpgradeRestriction = z.infer; diff --git a/frontend/types/schemas/project/Usemap.ts b/frontend/types/schemas/project/Usemap.ts index 8374e35..21c65d5 100644 --- a/frontend/types/schemas/project/Usemap.ts +++ b/frontend/types/schemas/project/Usemap.ts @@ -118,8 +118,10 @@ export const UsemapSchema = z.object({ y: z.number().int().default(0), }), size: z.object({ - height: z.number().int(), - width: z.number().int(), + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), }), }) .describe("An entity component can have spatial data."), @@ -139,8 +141,10 @@ export const UsemapSchema = z.object({ y: z.number().int().default(0), }), size: z.object({ - height: z.number().int(), - width: z.number().int(), + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), }), }) .describe("An entity component can have spatial data."), @@ -424,8 +428,10 @@ export const UsemapSchema = z.object({ y: z.number().int().default(0), }), size: z.object({ - height: z.number().int(), - width: z.number().int(), + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), }), }) .describe("An entity component can have spatial data."), @@ -505,8 +511,10 @@ export const UsemapSchema = z.object({ y: z.number().int().default(0), }), size: z.object({ - height: z.number().int(), - width: z.number().int(), + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), }), }) .describe("An entity component can have spatial data."), @@ -526,8 +534,10 @@ export const UsemapSchema = z.object({ y: z.number().int().default(0), }), size: z.object({ - height: z.number().int(), - width: z.number().int(), + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), }), }) .describe("An entity component can have spatial data."), @@ -547,8 +557,10 @@ export const UsemapSchema = z.object({ y: z.number().int().default(0), }), size: z.object({ - height: z.number().int(), - width: z.number().int(), + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), }), }) .describe("An entity component can have spatial data."), @@ -568,7 +580,806 @@ export const UsemapSchema = z.object({ type: z.enum(["folder", "file"]), preview: z.union([z.number().int(), z.null()]).default(null), parent_id: z.number().int().default(0), - data: z.union([z.object({}), z.null()]).default(null), + data: z + .union([ + z + .object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + }) + .describe("Object for saving data to class."), + z + .object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean().default(true), + specification: z.object({ + name: z.string().default("Unit Specification"), + graphics: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + sprite: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([ + z.number().int(), + z.null(), + ]), + selection_circle_offset: z.union([ + z.number().int(), + z.null(), + ]), + }), + top_speed: z.number().int(), + acceleration: z.number().int(), + halt_distance: z.number().int(), + turn_radius: z.number().int(), + unused: z.number().int(), + move_control: z.number().int(), + }), + subunit1: z.number().int(), + subunit2: z.number().int(), + infestation: z.union([z.number().int(), z.null()]), + construction_animation: z.number().int(), + unit_direction: z.number().int(), + portrait: z.number().int(), + label: z.number().int(), + }), + stats: z.object({ + name: z.string().default("Unit Status"), + hit_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + shield_enable: z.boolean(), + shield_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + energy_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + armor_points: z.number().int().lt(256).default(0), + armor_upgrade: z.number().int(), + rank: z.number().int(), + elevation_level: z.number().int(), + }), + weapons: z.object({ + ground_weapon: z.union([ + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.null(), + ]), + max_ground_hits: z.number().int(), + air_weapon: z.union([ + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.null(), + ]), + max_air_hits: z.number().int(), + target_acquisition_range: z.number().int(), + sight_range: z.number().int(), + special_ability_flags: z.number().int(), + }), + ai: z.object({ + name: z.string().default("Unit AI"), + computer_idle: z.number().int(), + human_idle: z.number().int(), + return_to_idle: z.number().int(), + attack_unit: z.number().int(), + attack_and_move: z.number().int(), + internal: z.number().int(), + right_click: z.number().int(), + }), + sound: z.object({ + name: z.string().default("Unit Sound"), + ready: z.union([z.number().int(), z.null()]), + what_start: z.number().int(), + what_end: z.number().int(), + piss_start: z.union([z.number().int(), z.null()]), + piss_end: z.union([z.number().int(), z.null()]), + yes_start: z.union([z.number().int(), z.null()]), + yes_end: z.union([z.number().int(), z.null()]), + }), + size: z.object({ + name: z.string().default("Unit Size"), + size_type: z.number().int(), + placement_box_size: z.object({ + height: z.number().int(), + width: z.number().int(), + }), + bounds: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), + addon_position: z.union([ + z.object({ + x: z.number().int().default(0), + y: z.number().int().default(0), + }), + z.null(), + ]), + }), + cost: z.object({ + name: z.string().default("Unit Cost"), + cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + build_score: z.number().int(), + destroy_score: z.number().int(), + is_broodwar: z.boolean(), + supply: z.object({ + required: z.number().int(), + provided: z.number().int(), + }), + space: z.object({ + required: z.number().int(), + provided: z.number().int(), + }), + }), + }) + .describe("Definition of unit specification."), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([z.number().int(), z.null()]), + selection_circle_offset: z.union([z.number().int(), z.null()]), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean(), + cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + energy: z.number().int().gte(0).default(0), + }), + energy_required: z.boolean(), + icon: z.number().int(), + label: z.number().int(), + race: z.number().int(), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + sprite: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([z.number().int(), z.null()]), + selection_circle_offset: z.union([z.number().int(), z.null()]), + }), + top_speed: z.number().int(), + acceleration: z.number().int(), + halt_distance: z.number().int(), + turn_radius: z.number().int(), + unused: z.number().int(), + move_control: z.number().int(), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + label: z.number().int(), + use_weapon_targeting: z.boolean(), + can_be_interrupted: z.boolean(), + can_be_queued: z.boolean(), + targeting: z.number().int(), + energy: z.number().int(), + animation: z.number().int(), + highlight: z.number().int(), + obscured_order: z.number().int(), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + player_maximum_level: z.array(z.number().int()).min(12).max(12), + player_minimum_level: z.array(z.number().int()).min(12).max(12), + default_maximum_level: z.number().int(), + default_minimum_level: z.number().int(), + uses_default: z.array(z.boolean()).min(12).max(12), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + player_availability: z.array(z.boolean()).min(12).max(12), + player_already_researched: z.array(z.boolean()).min(12).max(12), + default_availability: z.boolean(), + default_already_researched: z.boolean(), + uses_default: z.array(z.boolean()).min(12).max(12), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + availability: z.array(z.boolean()), + global_availability: z.boolean(), + uses_defaults: z.array(z.boolean()), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean(), + base_cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + factor_cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + icon: z.number().int(), + label: z.number().int(), + race: z.number().int(), + }), + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + portrait_file: z.number().int(), + smk_change: z.number().int(), + unknown1: z.number().int(), + }), + z + .object({ + id: z.number().int().default(0), + name: z.string().default("Object"), + transform: z + .object({ + id: z.number().int().default(0), + name: z.string().default("Object"), + position: z.object({ + x: z.number().int().default(0), + y: z.number().int().default(0), + }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), + }) + .describe("An entity component can have spatial data."), + kind: z + .enum(["Unit", "Sprite", "Location", "Tile", "Mask"]) + .default("Unit"), + serial_number: z + .union([z.number().int(), z.null()]) + .default(null), + use_default: z.boolean().default(true), + unit_definition: z + .object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + use_default: z.boolean().default(true), + specification: z.object({ + name: z.string().default("Unit Specification"), + graphics: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + sprite: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z + .literal("Definition") + .default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([ + z.number().int(), + z.null(), + ]), + selection_circle_offset: z.union([ + z.number().int(), + z.null(), + ]), + }), + top_speed: z.number().int(), + acceleration: z.number().int(), + halt_distance: z.number().int(), + turn_radius: z.number().int(), + unused: z.number().int(), + move_control: z.number().int(), + }), + subunit1: z.number().int(), + subunit2: z.number().int(), + infestation: z.union([z.number().int(), z.null()]), + construction_animation: z.number().int(), + unit_direction: z.number().int(), + portrait: z.number().int(), + label: z.number().int(), + }), + stats: z.object({ + name: z.string().default("Unit Status"), + hit_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + shield_enable: z.boolean(), + shield_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + energy_points: z.object({ + current: z.number().int().gte(0).default(0), + max: z.number().int().gte(0).default(0), + }), + armor_points: z.number().int().lt(256).default(0), + armor_upgrade: z.number().int(), + rank: z.number().int(), + elevation_level: z.number().int(), + }), + weapons: z.object({ + ground_weapon: z.union([ + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.null(), + ]), + max_ground_hits: z.number().int(), + air_weapon: z.union([ + z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), + }), + z.null(), + ]), + max_air_hits: z.number().int(), + target_acquisition_range: z.number().int(), + sight_range: z.number().int(), + special_ability_flags: z.number().int(), + }), + ai: z.object({ + name: z.string().default("Unit AI"), + computer_idle: z.number().int(), + human_idle: z.number().int(), + return_to_idle: z.number().int(), + attack_unit: z.number().int(), + attack_and_move: z.number().int(), + internal: z.number().int(), + right_click: z.number().int(), + }), + sound: z.object({ + name: z.string().default("Unit Sound"), + ready: z.union([z.number().int(), z.null()]), + what_start: z.number().int(), + what_end: z.number().int(), + piss_start: z.union([z.number().int(), z.null()]), + piss_end: z.union([z.number().int(), z.null()]), + yes_start: z.union([z.number().int(), z.null()]), + yes_end: z.union([z.number().int(), z.null()]), + }), + size: z.object({ + name: z.string().default("Unit Size"), + size_type: z.number().int(), + placement_box_size: z.object({ + height: z.number().int(), + width: z.number().int(), + }), + bounds: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), + addon_position: z.union([ + z.object({ + x: z.number().int().default(0), + y: z.number().int().default(0), + }), + z.null(), + ]), + }), + cost: z.object({ + name: z.string().default("Unit Cost"), + cost: z.object({ + mineral: z.number().int().gte(0).default(0), + gas: z.number().int().gte(0).default(0), + time: z.number().int().gte(0).default(0), + }), + build_score: z.number().int(), + destroy_score: z.number().int(), + is_broodwar: z.boolean(), + supply: z.object({ + required: z.number().int(), + provided: z.number().int(), + }), + space: z.object({ + required: z.number().int(), + provided: z.number().int(), + }), + }), + }) + .describe("Definition of unit specification."), + owner: z + .object({ + id: z.number().int().default(0), + name: z.string().default("Object"), + color: z.number().int(), + rgb_color: z.array(z.any()).min(3).max(3), + player_type: z.enum([ + "Inactive", + "Computer (game)", + "Occupied By Human Player", + "Rescue", + "Unused", + "Computer", + "Human (Open Slot)", + "Neutral", + "Closed Slot", + ]), + race: z.enum([ + "Zerg", + "Terran", + "Protoss", + "Invalid (Independant)", + "Invalid (Neutral)", + "User Selectable", + "Random", + "Inactive", + ]), + force: z.number().int().gte(0).lt(4).default(0), + }) + .optional(), + resource_amount: z.number().int().default(0), + hangar: z.number().int().default(0), + unit_state: z.number().int().default(0), + relation_type: z.number().int().default(0), + related_unit: z.number().int().default(0), + special_properties: z.number().int().default(0), + valid_properties: z.number().int().default(0), + }) + .describe( + "Unit placed on map.\n\nThe entity means what placeable on map, so every `Unit` which herit `Entity` is placed unit.\nIf you looking for specificaiton of unit like `Max HP`, `Size`, see `UnitDefinition`.", + ), + z + .object({ + id: z.number().int().default(0), + name: z.string().default("Object"), + transform: z + .object({ + id: z.number().int().default(0), + name: z.string().default("Object"), + position: z.object({ + x: z.number().int().default(0), + y: z.number().int().default(0), + }), + size: z.object({ + left: z.number().int(), + top: z.number().int(), + right: z.number().int(), + bottom: z.number().int(), + }), + }) + .describe("An entity component can have spatial data."), + kind: z + .enum(["Unit", "Sprite", "Location", "Tile", "Mask"]) + .default("Sprite"), + owner: z.object({ + id: z.number().int().default(0), + name: z.string().default("Object"), + color: z.number().int(), + rgb_color: z.array(z.any()).min(3).max(3), + player_type: z.enum([ + "Inactive", + "Computer (game)", + "Occupied By Human Player", + "Rescue", + "Unused", + "Computer", + "Human (Open Slot)", + "Neutral", + "Closed Slot", + ]), + race: z.enum([ + "Zerg", + "Terran", + "Protoss", + "Invalid (Independant)", + "Invalid (Neutral)", + "User Selectable", + "Random", + "Inactive", + ]), + force: z.number().int().gte(0).lt(4).default(0), + }), + flags: z.number().int(), + definition: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + image: z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + graphic: z.number().int(), + turnable: z.boolean(), + clickable: z.boolean(), + use_full_iscript: z.boolean(), + draw_if_cloaked: z.boolean(), + draw_function: z.number().int(), + remapping: z.number().int(), + iscript_id: z.number().int(), + shield_overlay: z.number().int(), + attack_overlay: z.number().int(), + damage_overlay: z.number().int(), + special_overlay: z.number().int(), + landing_dust_overlay: z.number().int(), + lift_off_overlay: z.number().int(), + }), + health_bar_id: z.union([z.number().int(), z.null()]), + selection_circle_image_id: z.union([ + z.number().int(), + z.null(), + ]), + selection_circle_offset: z.union([z.number().int(), z.null()]), + }), + }) + .describe("Placed Sprite Entity."), + z.null(), + ]) + .default(null), }), ), }); diff --git a/frontend/types/schemas/project/WeaponDefinition.ts b/frontend/types/schemas/project/WeaponDefinition.ts new file mode 100644 index 0000000..2f73b6c --- /dev/null +++ b/frontend/types/schemas/project/WeaponDefinition.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; + +export const WeaponDefinitionSchema = z.object({ + id: z.number().int().default(0), + name: z.string().default("Definition"), + ref_type: z.literal("Definition").default("Definition"), + damage: z.object({ + amount: z.number().int().gte(0).lte(65536), + bonus: z.number().int().gte(0).lte(65536), + factor: z.number().int(), + }), + bullet: z.object({ + behaviour: z.number().int(), + remove_after: z.number().int(), + attack_angle: z.number().int(), + launch_spin: z.number().int(), + x_offset: z.number().int(), + y_offset: z.number().int(), + }), + splash: z.object({ + inner: z.number().int(), + medium: z.number().int(), + outer: z.number().int(), + }), + cooldown: z.number().int(), + upgrade: z.number().int(), + weapon_type: z.number().int(), + explosion_type: z.number().int(), + target_flags: z.number().int(), + error_message: z.number().int(), + icon: z.number().int(), + graphics: z.number().int(), +}); +export type WeaponDefinition = z.infer;