diff --git a/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts b/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts index 23b099d..e986f92 100644 --- a/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts +++ b/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts @@ -10,9 +10,15 @@ import { } from "boardgame-core/samples/slay-the-spire-like"; import { DisposableBag } from "../../../framework/dist"; import { InventoryItemState } from "@/state/InventoryItemState"; +import { effect } from "@preact/signals-core"; export interface InventoryItemContainerCallbacks { - onMoveItem: (itemId: string, newX: number, newY: number) => boolean; + onMoveItem: ( + itemId: string, + newX: number, + newY: number, + newRotation: number, + ) => boolean; } export class InventoryItemContainer extends Phaser.GameObjects.Container { @@ -44,21 +50,25 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { label.setText(this.itemState.name.value); }); - disposables.addEffect(() => { - graphics.clear(); - if (!this.itemState.shape.value) return; - const cellRects = this.renderGraphics( - graphics, - this.itemState.shape.value, - this.itemState.color.value, - ); + disposables.add( + effect(() => { + graphics.clear(); + if (!this.itemState.shape.value) return; + const cellRects = this.renderGraphics( + graphics, + this.itemState.shape.value, + this.itemState.color.value, + this.itemState.previewRotation.value, + ); - this.setupInteractive(cellRects); - }); + return this.setupInteractive(cellRects); + }), + ); disposables.addEffect(() => { if (!this.itemState.transform.value) return; this.snapBack(this.itemState.transform.value); + this.itemState.setPreviewRotation(0); }); } @@ -70,8 +80,15 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { graphics: Phaser.GameObjects.Graphics, shape: ParsedShape, itemColor: number, + rotation: number, ): { x: number; y: number }[] { - const cells = transformShape(shape, IDENTITY_TRANSFORM); + const transform: Transform2D = { + offset: { x: 0, y: 0 }, + rotation, + flipX: false, + flipY: false, + }; + const cells = transformShape(shape, transform); const cellRects: { x: number; y: number }[] = []; for (const cell of cells) { @@ -92,7 +109,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { return cellRects; } - private setupInteractive(cellRects: { x: number; y: number }[]): void { + private setupInteractive(cellRects: { x: number; y: number }[]) { this.setScrollFactor(0); const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE; this.setInteractive({ @@ -114,7 +131,8 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { let startX = 0; let startY = 0; - dragDropEventEffect(this, (event) => { + return dragDropEventEffect(this, (event) => { + console.log(event.type); if (event.type === DragDropEventType.DOWN) { startX = this.x; startY = this.y; @@ -122,18 +140,25 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { } else if (event.type === DragDropEventType.MOVE) { this.x = startX + event.deltaX; this.y = startY + event.deltaY; + } else if (event.type === DragDropEventType.ALTBUTTON) { + const current = this.itemState.previewRotation.peek(); + this.itemState.setPreviewRotation(current + 90); } else if (event.type === DragDropEventType.UP) { this.setAlpha(1); - if (!this.handleDragEnd()) { + const finalRotation = this.itemState.previewRotation.peek(); + if (!this.handleDragEnd(finalRotation)) { + this.itemState.setPreviewRotation(0); const t = this.itemState.transform.peek(); t && this.snapBack(t); + } else { + this.itemState.setPreviewRotation(0); } startX = startY = 0; } }); } - private handleDragEnd(): boolean { + private handleDragEnd(finalRotation: number): boolean { const item = this.itemState.item; if (!item) return false; @@ -151,9 +176,15 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { if ( clampedX !== item.transform.offset.x || - clampedY !== item.transform.offset.y + clampedY !== item.transform.offset.y || + finalRotation !== 0 ) { - return this.callbacks.onMoveItem(item.id, clampedX, clampedY); + return this.callbacks.onMoveItem( + item.id, + clampedX, + clampedY, + finalRotation, + ); } return false; } diff --git a/packages/sts-like-viewer/src/gameobjects/InventoryItemSpawner.ts b/packages/sts-like-viewer/src/gameobjects/InventoryItemSpawner.ts index e32df2e..e5e14b8 100644 --- a/packages/sts-like-viewer/src/gameobjects/InventoryItemSpawner.ts +++ b/packages/sts-like-viewer/src/gameobjects/InventoryItemSpawner.ts @@ -42,8 +42,8 @@ export class InventoryItemSpawner implements Spawner< this.gridOffsetX, this.gridOffsetY, { - onMoveItem: (id, newX, newY) => { - return this.callbacks.onMoveItem(id, newX, newY); + onMoveItem: (id, newX, newY, newRotation) => { + return this.callbacks.onMoveItem(id, newX, newY, newRotation); }, }, ); diff --git a/packages/sts-like-viewer/src/scenes/InventoryTestScene.ts b/packages/sts-like-viewer/src/scenes/InventoryTestScene.ts index 53a88e2..386b74c 100644 --- a/packages/sts-like-viewer/src/scenes/InventoryTestScene.ts +++ b/packages/sts-like-viewer/src/scenes/InventoryTestScene.ts @@ -93,8 +93,19 @@ export class InventoryTestScene extends ReactiveScene { this.gridOffsetX, this.gridOffsetY, { - onMoveItem: (itemId: string, newX: number, newY: number) => { - return moveItem(this.inventorySignal, itemId, newX, newY); + onMoveItem: ( + itemId: string, + newX: number, + newY: number, + newRotation: number, + ) => { + return moveItem( + this.inventorySignal, + itemId, + newX, + newY, + newRotation, + ); }, }, ); diff --git a/packages/sts-like-viewer/src/state/InventoryItemState.ts b/packages/sts-like-viewer/src/state/InventoryItemState.ts index 65f6f35..93af969 100644 --- a/packages/sts-like-viewer/src/state/InventoryItemState.ts +++ b/packages/sts-like-viewer/src/state/InventoryItemState.ts @@ -1,44 +1,57 @@ -import { computed, signal, Signal } from "@preact/signals-core" -import { ITEM_COLORS } from "@/config" +import { computed, ReadonlySignal, signal, Signal } from "@preact/signals-core"; +import { ITEM_COLORS } from "@/config"; import { type GameItem, type ParsedShape, type Transform2D, -} from "boardgame-core/samples/slay-the-spire-like" +} from "boardgame-core/samples/slay-the-spire-like"; export class InventoryItemState { - private readonly _item: Signal + private readonly _item: Signal; + private readonly _previewRotation: Signal; - readonly name: Signal - readonly shape: Signal - readonly color: Signal - readonly transform: Signal + readonly name: ReadonlySignal; + readonly shape: ReadonlySignal; + readonly color: ReadonlySignal; + readonly transform: ReadonlySignal; + readonly previewRotation: ReadonlySignal; constructor(initialItem?: GameItem) { - this._item = signal(initialItem) + this._item = signal(initialItem); + this._previewRotation = signal(0); this.name = computed(() => { - const item = this._item.value - return item?.meta?.itemData.name ?? item?.id ?? "" - }) + const item = this._item.value; + return item?.meta?.itemData.name ?? item?.id ?? ""; + }); - this.shape = computed(() => this._item.value?.shape) + this.shape = computed(() => this._item.value?.shape); - this.color = computed(() => this.computeColor(this._item.value?.id ?? "")) + this.color = computed(() => this.computeColor(this._item.value?.id ?? "")); - this.transform = computed(() => this._item.value?.transform) + this.transform = computed(() => this._item.value?.transform); + + this.previewRotation = computed(() => { + const base = this._item.value?.transform?.rotation ?? 0; + return base + this._previewRotation.value; + }); } get item(): GameItem | undefined { - return this._item.value + return this._item.value; } setItem(item: GameItem): void { - this._item.value = item + this._item.value = item; + this._previewRotation.value = 0; + } + + setPreviewRotation(rotation: number): void { + this._previewRotation.value = rotation; } private computeColor(itemId: string): number { - const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0) - return ITEM_COLORS[hash % ITEM_COLORS.length] + const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0); + return ITEM_COLORS[hash % ITEM_COLORS.length]; } } diff --git a/packages/sts-like-viewer/src/state/inventory.ts b/packages/sts-like-viewer/src/state/inventory.ts index bcc56f3..161bab9 100644 --- a/packages/sts-like-viewer/src/state/inventory.ts +++ b/packages/sts-like-viewer/src/state/inventory.ts @@ -7,6 +7,7 @@ import { GameItemMeta, placeItem, removeItemFromGrid, + Transform2D, validatePlacement, } from "boardgame-core/samples/slay-the-spire-like"; @@ -36,6 +37,7 @@ export function moveItem( itemId: string, newX: number, newY: number, + newRotation?: number, ): boolean { const inventory = inventorySignal.value; const item = inventory.items.get(itemId); @@ -44,9 +46,11 @@ export function moveItem( return false; } - const newTransform = { - ...item.transform, + const newTransform: Transform2D = { offset: { x: newX, y: newY }, + rotation: newRotation === undefined ? item.transform.rotation : newRotation, + flipX: false, + flipY: false, }; const removed = create(inventory, (inv) => {