refactor: support multiple inventory surfaces
Introduce `InventorySurface` to allow items to be moved between different inventory grids. - Update `InventoryItemContainer` to use surface-specific cell sizes and coordinate transformations. - Update `InventoryItemSpawner` to handle multiple surfaces. - Refactor `moveItem` to support transferring items from one `InventorySignal` to another. - Add `InventorySurfaceState` to manage surface-specific metadata. - Remove reliance on global `GRID_CONFIG` in favor of surface data.
This commit is contained in:
parent
093eadbefd
commit
422ddde200
|
|
@ -11,44 +11,44 @@ import {
|
||||||
} from "boardgame-core/samples/slay-the-spire-like";
|
} from "boardgame-core/samples/slay-the-spire-like";
|
||||||
import { DisposableBag } from "../../../framework/dist";
|
import { DisposableBag } from "../../../framework/dist";
|
||||||
import { InventoryItemState } from "@/state/InventoryItemState";
|
import { InventoryItemState } from "@/state/InventoryItemState";
|
||||||
|
import { moveItem } from "@/state/inventory";
|
||||||
export interface InventoryItemContainerCallbacks {
|
import {
|
||||||
onMoveItem: (
|
InventorySurface,
|
||||||
itemId: string,
|
InventorySurfaceState,
|
||||||
newX: number,
|
inventoryToScene,
|
||||||
newY: number,
|
sceneToInventory,
|
||||||
newRotation: number,
|
} from "@/state/inventorySurfaceState";
|
||||||
) => boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
private itemState: InventoryItemState;
|
private itemState: InventoryItemState;
|
||||||
|
private surfaceState: InventorySurfaceState;
|
||||||
|
private surfaces: Iterable<InventorySurface>;
|
||||||
private hitArea: Point2D[] = [];
|
private hitArea: Point2D[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
scene: Phaser.Scene,
|
scene: Phaser.Scene,
|
||||||
private gridOffsetX: number,
|
item: GameItem,
|
||||||
private gridOffsetY: number,
|
surface: InventorySurface,
|
||||||
private callbacks: InventoryItemContainerCallbacks,
|
surfaces: Iterable<InventorySurface>,
|
||||||
) {
|
) {
|
||||||
super(scene, gridOffsetX, gridOffsetY);
|
super(scene, surface.gridOffsetX, surface.gridOffsetY);
|
||||||
scene.add.existing(this);
|
scene.add.existing(this);
|
||||||
|
|
||||||
|
const cellSize = surface.cellSize;
|
||||||
const graphics = this.scene.add.graphics();
|
const graphics = this.scene.add.graphics();
|
||||||
graphics.setPosition(
|
graphics.setPosition(cellSize / 2, cellSize / 2);
|
||||||
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
|
||||||
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
|
||||||
);
|
|
||||||
const label = this.scene.add.text(
|
const label = this.scene.add.text(
|
||||||
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
cellSize / 2,
|
||||||
GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
cellSize / 2,
|
||||||
"",
|
"",
|
||||||
GRID_CONFIG.ITEM_NAME_STYLE,
|
GRID_CONFIG.ITEM_NAME_STYLE,
|
||||||
);
|
);
|
||||||
this.add([graphics, label]);
|
this.add([graphics, label]);
|
||||||
this.setupInteractive();
|
this.setupInteractive();
|
||||||
|
|
||||||
this.itemState = new InventoryItemState();
|
this.itemState = new InventoryItemState(item);
|
||||||
|
this.surfaceState = new InventorySurfaceState(surface);
|
||||||
|
this.surfaces = surfaces;
|
||||||
|
|
||||||
const disposables = new DisposableBag(this);
|
const disposables = new DisposableBag(this);
|
||||||
|
|
||||||
|
|
@ -60,9 +60,9 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
|
|
||||||
disposables.addEffect(() => {
|
disposables.addEffect(() => {
|
||||||
graphics.clear();
|
graphics.clear();
|
||||||
if (!this.itemState.shape.value) return;
|
|
||||||
this.renderGraphics(
|
this.renderGraphics(
|
||||||
graphics,
|
graphics,
|
||||||
|
this.surfaceState.cellSize,
|
||||||
this.itemState.shape.value,
|
this.itemState.shape.value,
|
||||||
this.itemState.color.value,
|
this.itemState.color.value,
|
||||||
);
|
);
|
||||||
|
|
@ -79,13 +79,13 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
});
|
});
|
||||||
|
|
||||||
disposables.addEffect(() => {
|
disposables.addEffect(() => {
|
||||||
if (!this.itemState.transform.value) return;
|
|
||||||
this.snapBack(this.itemState.transform.value);
|
this.snapBack(this.itemState.transform.value);
|
||||||
this.itemState.setPreviewRotation(0);
|
this.itemState.setPreviewRotation(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setItem(item: GameItem): void {
|
setSurfaceItem(surface: InventorySurface, item: GameItem): void {
|
||||||
|
this.surfaceState.setSurface(surface);
|
||||||
this.itemState.setItem(item);
|
this.itemState.setItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
flipX: false,
|
flipX: false,
|
||||||
flipY: false,
|
flipY: false,
|
||||||
});
|
});
|
||||||
const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE;
|
const cellSize = this.surfaceState.cellSize;
|
||||||
|
|
||||||
return cells.map((cell) => ({
|
return cells.map((cell) => ({
|
||||||
x: (cell.x - cells[0].x) * cellSize,
|
x: (cell.x - cells[0].x) * cellSize,
|
||||||
|
|
@ -110,28 +110,28 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
|
|
||||||
private renderGraphics(
|
private renderGraphics(
|
||||||
graphics: Phaser.GameObjects.Graphics,
|
graphics: Phaser.GameObjects.Graphics,
|
||||||
|
cellSize: number,
|
||||||
shape: ParsedShape,
|
shape: ParsedShape,
|
||||||
itemColor: number,
|
itemColor: number,
|
||||||
) {
|
) {
|
||||||
const cells = transformShape(shape, IDENTITY_TRANSFORM);
|
const cells = transformShape(shape, IDENTITY_TRANSFORM);
|
||||||
|
|
||||||
for (const cell of cells) {
|
for (const cell of cells) {
|
||||||
const localX = (cell.x - cells[0].x) * GRID_CONFIG.VIEWER_CELL_SIZE;
|
const localX = (cell.x - cells[0].x) * cellSize;
|
||||||
const localY = (cell.y - cells[0].y) * GRID_CONFIG.VIEWER_CELL_SIZE;
|
const localY = (cell.y - cells[0].y) * cellSize;
|
||||||
|
|
||||||
graphics.fillStyle(itemColor);
|
graphics.fillStyle(itemColor);
|
||||||
graphics.fillRect(
|
graphics.fillRect(
|
||||||
localX + 2 - GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
localX + 2 - cellSize / 2,
|
||||||
localY + 2 - GRID_CONFIG.VIEWER_CELL_SIZE / 2,
|
localY + 2 - cellSize / 2,
|
||||||
GRID_CONFIG.VIEWER_CELL_SIZE - 4,
|
cellSize - 4,
|
||||||
GRID_CONFIG.VIEWER_CELL_SIZE - 4,
|
cellSize - 4,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupInteractive() {
|
private setupInteractive() {
|
||||||
this.setScrollFactor(0);
|
this.setScrollFactor(0);
|
||||||
const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
||||||
this.setInteractive({
|
this.setInteractive({
|
||||||
hitArea: this,
|
hitArea: this,
|
||||||
hitAreaCallback: (
|
hitAreaCallback: (
|
||||||
|
|
@ -142,9 +142,9 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
hitArea.hitArea.some(
|
hitArea.hitArea.some(
|
||||||
(cell) =>
|
(cell) =>
|
||||||
x >= cell.x &&
|
x >= cell.x &&
|
||||||
x < cell.x + cellSize &&
|
x < cell.x + this.surfaceState.cellSize &&
|
||||||
y >= cell.y &&
|
y >= cell.y &&
|
||||||
y < cell.y + cellSize,
|
y < cell.y + this.surfaceState.cellSize,
|
||||||
),
|
),
|
||||||
useHandCursor: true,
|
useHandCursor: true,
|
||||||
} as Phaser.Types.Input.InputConfiguration);
|
} as Phaser.Types.Input.InputConfiguration);
|
||||||
|
|
@ -164,58 +164,68 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container {
|
||||||
} else if (event.type === DragDropEventType.ALTBUTTON) {
|
} else if (event.type === DragDropEventType.ALTBUTTON) {
|
||||||
this.itemState.addPreviewRotation(90);
|
this.itemState.addPreviewRotation(90);
|
||||||
} else if (event.type === DragDropEventType.UP) {
|
} else if (event.type === DragDropEventType.UP) {
|
||||||
this.setAlpha(1);
|
|
||||||
const finalRotation = this.itemState.previewRotation.peek();
|
const finalRotation = this.itemState.previewRotation.peek();
|
||||||
if (!this.handleDragEnd(finalRotation)) {
|
const finalX = this.x;
|
||||||
|
const finalY = this.y;
|
||||||
|
if (!this.handleDragEnd(finalX, finalY, finalRotation)) {
|
||||||
const t = this.itemState.transform.peek();
|
const t = this.itemState.transform.peek();
|
||||||
t && this.snapBack(t);
|
t && this.snapBack(t);
|
||||||
}
|
}
|
||||||
|
this.setAlpha(1);
|
||||||
this.itemState.setPreviewRotation(0);
|
this.itemState.setPreviewRotation(0);
|
||||||
startX = startY = 0;
|
startX = startY = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleDragEnd(finalRotation: number): boolean {
|
private handleDragEnd(
|
||||||
|
finalX: number,
|
||||||
|
finalY: number,
|
||||||
|
finalRotation: number,
|
||||||
|
): boolean {
|
||||||
const item = this.itemState.item;
|
const item = this.itemState.item;
|
||||||
if (!item) return false;
|
if (!item) return false;
|
||||||
|
|
||||||
const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE;
|
const target = this.findDropSurface(finalX, finalY);
|
||||||
const shapeWidth = item.shape?.width ?? 1;
|
if (!target) return false;
|
||||||
const shapeHeight = item.shape?.height ?? 1;
|
|
||||||
|
|
||||||
const x = this.x - this.gridOffsetX;
|
|
||||||
const y = this.y - this.gridOffsetY;
|
|
||||||
const targetX = Math.round(x / cellSize);
|
|
||||||
const targetY = Math.round(y / cellSize);
|
|
||||||
|
|
||||||
const clampedX = Math.max(0, Math.min(targetX, 10 - shapeWidth));
|
|
||||||
const clampedY = Math.max(0, Math.min(targetY, 10 - shapeHeight));
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
clampedX !== item.transform.offset.x ||
|
this.surfaceState.surface === target.surface &&
|
||||||
clampedY !== item.transform.offset.y ||
|
item.transform.offset.x === target.x &&
|
||||||
finalRotation !== item.transform.rotation
|
item.transform.offset.y === target.x &&
|
||||||
) {
|
item.transform.rotation === finalRotation
|
||||||
return this.callbacks.onMoveItem(
|
)
|
||||||
item.id,
|
return false;
|
||||||
clampedX,
|
|
||||||
clampedY,
|
return moveItem(
|
||||||
finalRotation,
|
this.surfaceState.invSignal,
|
||||||
);
|
target.surface.invSignal,
|
||||||
|
item.id,
|
||||||
|
target.x,
|
||||||
|
target.y,
|
||||||
|
finalRotation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private findDropSurface(x: number, y: number) {
|
||||||
|
for (const surface of this.surfaces) {
|
||||||
|
const target = sceneToInventory(surface, x, y);
|
||||||
|
if (target)
|
||||||
|
return {
|
||||||
|
surface,
|
||||||
|
...target,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private snapBack(transform: Transform2D): void {
|
private snapBack(transform: Transform2D): void {
|
||||||
const { x, y } = transform.offset;
|
const { x, y } = transform.offset;
|
||||||
const targetX = this.gridOffsetX + x * GRID_CONFIG.VIEWER_CELL_SIZE;
|
const target = inventoryToScene(this.surfaceState, x, y);
|
||||||
const targetY = this.gridOffsetY + y * GRID_CONFIG.VIEWER_CELL_SIZE;
|
|
||||||
|
|
||||||
this.scene.tweens.add({
|
this.scene.tweens.add({
|
||||||
targets: this,
|
targets: this,
|
||||||
x: targetX,
|
...target,
|
||||||
y: targetY,
|
|
||||||
duration: 150,
|
duration: 150,
|
||||||
ease: "Power2",
|
ease: "Power2",
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,64 +1,53 @@
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import type { Spawner } from "boardgame-phaser";
|
import type { Spawner } from "boardgame-phaser";
|
||||||
import type {
|
import type { GameItem } from "boardgame-core/samples/slay-the-spire-like";
|
||||||
GameItemMeta,
|
|
||||||
InventoryItem,
|
|
||||||
} from "boardgame-core/samples/slay-the-spire-like";
|
|
||||||
import type { InventorySignal } from "@/state/inventory";
|
|
||||||
import { spawnEffect } from "boardgame-phaser";
|
import { spawnEffect } from "boardgame-phaser";
|
||||||
import { InventoryItemContainer } from "./InventoryItemContainer";
|
import { InventoryItemContainer } from "./InventoryItemContainer";
|
||||||
import type { InventoryItemContainerCallbacks } from "./InventoryItemContainer";
|
import { InventorySurface } from "@/state/inventorySurfaceState";
|
||||||
|
|
||||||
export interface InventoryItemSpawnerCallbacks extends InventoryItemContainerCallbacks {}
|
|
||||||
|
|
||||||
export class InventoryItemSpawner implements Spawner<
|
export class InventoryItemSpawner implements Spawner<
|
||||||
[string, InventoryItem<GameItemMeta>],
|
[InventorySurface, GameItem],
|
||||||
InventoryItemContainer
|
InventoryItemContainer
|
||||||
> {
|
> {
|
||||||
constructor(
|
constructor(
|
||||||
private scene: Phaser.Scene,
|
private scene: Phaser.Scene,
|
||||||
private inventorySignal: InventorySignal,
|
private surfaces: Iterable<InventorySurface>,
|
||||||
private gridOffsetX: number,
|
|
||||||
private gridOffsetY: number,
|
|
||||||
private callbacks: InventoryItemSpawnerCallbacks,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
*getData(): Iterable<[string, InventoryItem<GameItemMeta>]> {
|
*getData(): Iterable<[InventorySurface, GameItem]> {
|
||||||
const inventory = this.inventorySignal.value;
|
for (const surface of this.surfaces) {
|
||||||
yield* inventory.items.entries();
|
const inv = surface.invSignal.value;
|
||||||
|
for (const item of inv.items.values()) {
|
||||||
|
yield [surface, item];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getKey(entry: [string, InventoryItem<GameItemMeta>]): string {
|
getKey(entry: [InventorySurface, GameItem]): string {
|
||||||
return entry[0];
|
return entry[1].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSpawn(
|
onSpawn(entry: [InventorySurface, GameItem]): InventoryItemContainer | null {
|
||||||
entry: [string, InventoryItem<GameItemMeta>],
|
const [surface, item] = entry;
|
||||||
): InventoryItemContainer | null {
|
|
||||||
const [itemId, item] = entry;
|
|
||||||
|
|
||||||
const container = new InventoryItemContainer(
|
const container = new InventoryItemContainer(
|
||||||
this.scene,
|
this.scene,
|
||||||
this.gridOffsetX,
|
item,
|
||||||
this.gridOffsetY,
|
surface,
|
||||||
{
|
this.surfaces,
|
||||||
onMoveItem: (id, newX, newY, newRotation) => {
|
|
||||||
return this.callbacks.onMoveItem(id, newX, newY, newRotation);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
container.setItem(item);
|
container.setSurfaceItem(surface, item);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate(
|
onUpdate(
|
||||||
entry: [string, InventoryItem<GameItemMeta>],
|
entry: [InventorySurface, GameItem],
|
||||||
container: InventoryItemContainer,
|
container: InventoryItemContainer,
|
||||||
): void {
|
): void {
|
||||||
const [itemId, item] = entry;
|
const [surface, item] = entry;
|
||||||
|
|
||||||
container.setItem(item);
|
container.setSurfaceItem(surface, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDespawn(container: InventoryItemContainer): void {
|
onDespawn(container: InventoryItemContainer): void {
|
||||||
|
|
@ -69,18 +58,7 @@ export class InventoryItemSpawner implements Spawner<
|
||||||
|
|
||||||
export function createInventoryItemSpawner(
|
export function createInventoryItemSpawner(
|
||||||
scene: Phaser.Scene,
|
scene: Phaser.Scene,
|
||||||
inventorySignal: InventorySignal,
|
surfaces: Iterable<InventorySurface>,
|
||||||
gridOffsetX: number,
|
|
||||||
gridOffsetY: number,
|
|
||||||
callbacks: InventoryItemSpawnerCallbacks,
|
|
||||||
) {
|
) {
|
||||||
return spawnEffect(
|
return spawnEffect(new InventoryItemSpawner(scene, surfaces));
|
||||||
new InventoryItemSpawner(
|
|
||||||
scene,
|
|
||||||
inventorySignal,
|
|
||||||
gridOffsetX,
|
|
||||||
gridOffsetY,
|
|
||||||
callbacks,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,37 +7,37 @@ import {
|
||||||
} from "boardgame-core/samples/slay-the-spire-like";
|
} from "boardgame-core/samples/slay-the-spire-like";
|
||||||
|
|
||||||
export class InventoryItemState {
|
export class InventoryItemState {
|
||||||
private readonly _item: Signal<GameItem | undefined>;
|
private readonly _item: Signal<GameItem>;
|
||||||
private readonly _previewRotation: Signal<number>;
|
private readonly _previewRotation: Signal<number>;
|
||||||
|
|
||||||
readonly name: ReadonlySignal<string>;
|
readonly name: ReadonlySignal<string>;
|
||||||
readonly shape: ReadonlySignal<ParsedShape | undefined>;
|
readonly shape: ReadonlySignal<ParsedShape>;
|
||||||
readonly color: ReadonlySignal<number>;
|
readonly color: ReadonlySignal<number>;
|
||||||
readonly transform: ReadonlySignal<Transform2D | undefined>;
|
readonly transform: ReadonlySignal<Transform2D>;
|
||||||
readonly previewRotation: ReadonlySignal<number>;
|
readonly previewRotation: ReadonlySignal<number>;
|
||||||
|
|
||||||
constructor(initialItem?: GameItem) {
|
constructor(initialItem: GameItem) {
|
||||||
this._item = signal(initialItem);
|
this._item = signal(initialItem);
|
||||||
this._previewRotation = signal(0);
|
this._previewRotation = signal(0);
|
||||||
|
|
||||||
this.name = computed(() => {
|
this.name = computed(() => {
|
||||||
const item = this._item.value;
|
const item = this._item.value;
|
||||||
return item?.meta?.itemData.name ?? item?.id ?? "";
|
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(() => {
|
this.previewRotation = computed(() => {
|
||||||
const base = this._item.value?.transform?.rotation ?? 0;
|
const base = this._item.value.transform.rotation ?? 0;
|
||||||
return (base + this._previewRotation.value) % 360;
|
return (base + this._previewRotation.value) % 360;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get item(): GameItem | undefined {
|
get item(): GameItem {
|
||||||
return this._item.value;
|
return this._item.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,14 @@ function genId() {
|
||||||
|
|
||||||
export type InventorySignal = ReturnType<typeof createInventorySignal>;
|
export type InventorySignal = ReturnType<typeof createInventorySignal>;
|
||||||
|
|
||||||
export function createInventorySignal() {
|
export function createInventorySignal(giveStart = false) {
|
||||||
const inventory = createGridInventory<GameItemMeta>(4, 6);
|
const inventory = createGridInventory<GameItemMeta>(4, 6);
|
||||||
|
|
||||||
const startingItems = data.desert.getStartingItems();
|
if (giveStart) {
|
||||||
for (const d of startingItems) {
|
const startingItems = data.desert.getStartingItems();
|
||||||
createItemIn(inventory, `${d.id}-${genId()}`, d);
|
for (const d of startingItems) {
|
||||||
|
createItemIn(inventory, `${d.id}-${genId()}`, d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mutableSignal(inventory);
|
return mutableSignal(inventory);
|
||||||
|
|
@ -33,13 +35,14 @@ export function createInventorySignal() {
|
||||||
* Returns true if the move was successful, false if the new position is invalid.
|
* Returns true if the move was successful, false if the new position is invalid.
|
||||||
*/
|
*/
|
||||||
export function moveItem(
|
export function moveItem(
|
||||||
inventorySignal: InventorySignal,
|
from: InventorySignal,
|
||||||
|
to: InventorySignal,
|
||||||
itemId: string,
|
itemId: string,
|
||||||
newX: number,
|
newX: number,
|
||||||
newY: number,
|
newY: number,
|
||||||
newRotation?: number,
|
newRotation?: number,
|
||||||
): boolean {
|
): boolean {
|
||||||
const inventory = inventorySignal.value;
|
const inventory = from.value;
|
||||||
const item = inventory.items.get(itemId);
|
const item = inventory.items.get(itemId);
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
|
|
@ -53,17 +56,20 @@ export function moveItem(
|
||||||
flipY: false,
|
flipY: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const removed = create(inventory, (inv) => {
|
const removed = create(to.value, (inv) => {
|
||||||
removeItemFromGrid(inv, itemId);
|
removeItemFromGrid(inv, itemId);
|
||||||
});
|
});
|
||||||
const validation = validatePlacement(removed, item.shape, newTransform);
|
const validation = validatePlacement(removed, item.shape, newTransform);
|
||||||
if (!validation.valid) return false;
|
if (!validation.valid) return false;
|
||||||
|
|
||||||
inventorySignal.produce((inv) => {
|
from.produce((inv) => {
|
||||||
const item = inv.items.get(itemId)!;
|
|
||||||
removeItemFromGrid(inv, itemId);
|
removeItemFromGrid(inv, itemId);
|
||||||
item.transform = newTransform;
|
});
|
||||||
placeItem(inv, item);
|
to.produce((inv) => {
|
||||||
|
placeItem(inv, {
|
||||||
|
...item,
|
||||||
|
transform: newTransform,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { InventorySignal } from "./inventory";
|
||||||
|
import { MutableSignal, mutableSignal } from "boardgame-core";
|
||||||
|
|
||||||
|
export type InventorySurface = {
|
||||||
|
invSignal: InventorySignal;
|
||||||
|
gridOffsetX: number;
|
||||||
|
gridOffsetY: number;
|
||||||
|
cellSize: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class InventorySurfaceState {
|
||||||
|
private readonly _signal: MutableSignal<InventorySurface>;
|
||||||
|
|
||||||
|
constructor(init: InventorySurface) {
|
||||||
|
this._signal = mutableSignal(init);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get surface() {
|
||||||
|
return this._signal.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get invSignal() {
|
||||||
|
return this._signal.value.invSignal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get gridOffsetX() {
|
||||||
|
return this._signal.value.gridOffsetX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get gridOffsetY() {
|
||||||
|
return this._signal.value.gridOffsetY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cellSize() {
|
||||||
|
return this._signal.value.cellSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSurface(surface: InventorySurface) {
|
||||||
|
this._signal.value = surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sceneToInventory(
|
||||||
|
surface: InventorySurface | InventorySurfaceState,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
): { x: number; y: number } | null {
|
||||||
|
const invX = Math.round(x / surface.cellSize);
|
||||||
|
const invY = Math.round(y / surface.cellSize);
|
||||||
|
|
||||||
|
const { width, height } = surface.invSignal.peek();
|
||||||
|
if (invX < 0 || invY < 0 || invX >= width || invY >= height) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { x: invX, y: invY };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inventoryToScene(
|
||||||
|
surface: InventorySurface | InventorySurfaceState,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
): { x: number; y: number } {
|
||||||
|
return {
|
||||||
|
x: x * surface.cellSize + surface.gridOffsetX,
|
||||||
|
y: y * surface.cellSize + surface.gridOffsetY,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue