Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/app/models/components/transform.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .component import EntityComponent
from ..structs.spatial import Position2D, Size
from ..structs.spatial import Position2D, RectPosition


class TransformComponent(EntityComponent):
Expand All @@ -8,4 +8,4 @@ class TransformComponent(EntityComponent):
"""

position: Position2D
size: Size
size: RectPosition
8 changes: 4 additions & 4 deletions backend/app/services/rawdata/chk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
Expand Down Expand Up @@ -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,
)
Expand Down
34 changes: 27 additions & 7 deletions backend/app/services/rawdata/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand All @@ -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",
Expand All @@ -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,
Expand Down Expand Up @@ -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],
Expand All @@ -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],
Expand All @@ -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],
Expand All @@ -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],
Expand Down
18 changes: 14 additions & 4 deletions frontend/components/core/usemap-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,18 @@ function UsemapEditorMenu({
path: string[];
handleChange: (path: string[], value: any) => void;
}) {
const onChange = (e: ChangeEvent<HTMLInputElement>) => {
const onChangeString = (e: ChangeEvent<HTMLInputElement>) => {
const timeout = setTimeout(() => {
handleChange(path, e.target.value);
}, 500);
return () => clearTimeout(timeout);
};
const onChangeNumber = (e: ChangeEvent<HTMLInputElement>) => {
const timeout = setTimeout(() => {
handleChange(path, Number(e.target.value));
}, 500);
return () => clearTimeout(timeout);
};

const fixedLabel = label
.replace(/_/g, " ")
Expand Down Expand Up @@ -140,21 +146,25 @@ function UsemapEditorMenu({
<SidebarInput
type="checkbox"
defaultChecked={value}
onChange={onChange}
onChange={onChangeString}
/>
);
break;
case "number":
input = (
<SidebarInput type="number" defaultValue={value} onChange={onChange} />
<SidebarInput
type="number"
defaultValue={value}
onChange={onChangeNumber}
/>
);
break;
case "string": {
input = (
<SidebarInput
type={typeof value}
defaultValue={value}
onChange={onChange}
onChange={onChangeString}
/>
);
break;
Expand Down
71 changes: 69 additions & 2 deletions frontend/components/layout/viewport.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLCanvasElement | null>(null);
Expand Down Expand Up @@ -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,
Expand All @@ -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({
Expand All @@ -79,13 +144,15 @@ export const MapImage = ({ className }: { className?: string }) => {
<canvas
ref={viewportCanvasRef}
style={{
cursor: isDragging ? "grabbing" : "grab",
cursor: isDragging.current ? "grabbing" : "grab",
}}
className="h-full w-full"
onMouseDown={onMousedown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
onMouseLeave={onMouseUp}
onKeyDown={handleKeydown}
tabIndex={0}
/>
</div>
);
Expand Down
16 changes: 15 additions & 1 deletion frontend/hooks/useDragViewport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Viewport>,
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 };
};

Expand All @@ -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 };
Expand All @@ -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 };
Expand Down
Loading