feat(grid): add grid-aware server actions
addResource now auto-places deposited resources onto the structure grid (first-fit scan, fills existing stacks first, falls back to flat-list behavior for non-grid structures). New server actions: - placeResourceOnGrid — explicit placement at x,y,rotated - moveGridItem — move item to new position with optional rotation - mergeGridStacks — merge two stacks of the same resource - rotateGridItem — toggle rotation in-place - removeGridItem — delete a grid entry - splitGridStack — split a stack into two at an empty spot - autoArrangeGrid — re-pack all items into grid
This commit is contained in:
parent
7bcbd534e4
commit
d2222e8701
1 changed files with 553 additions and 13 deletions
|
|
@ -110,22 +110,105 @@ export async function addResource(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stored resources
|
// Auto-place on grid if structure has grid dimensions
|
||||||
const existing = [...(structure.storedResources ?? [])];
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
const existingIndex = existing.findIndex((e) => {
|
const gridW = structureType.xSize ?? 0;
|
||||||
const res = e.resource;
|
const gridH = structureType.ySize ?? 0;
|
||||||
return typeof res === "object" && res !== null && "id" in res
|
const hasGrid = gridW > 0 && gridH > 0;
|
||||||
? res.id === resourceId
|
|
||||||
: res === resourceId;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingIndex >= 0) {
|
if (hasGrid) {
|
||||||
existing[existingIndex] = {
|
const resW = resource.gridWidth ?? 1;
|
||||||
...existing[existingIndex],
|
const resH = resource.gridHeight ?? 1;
|
||||||
amount: existing[existingIndex].amount + amount,
|
const ew = resW;
|
||||||
};
|
const eh = resH;
|
||||||
|
const maxStack = resource.stackable === false
|
||||||
|
? 1
|
||||||
|
: resource.maxStackSize ?? Infinity;
|
||||||
|
|
||||||
|
// 1. Fill existing stacks of the same resource that have space
|
||||||
|
let remaining = amount;
|
||||||
|
const sameType = existing.filter((e) => {
|
||||||
|
const r = e.resource;
|
||||||
|
return typeof r === "object" && r !== null && "id" in r
|
||||||
|
? r.id === resourceId
|
||||||
|
: r === resourceId;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const entry of sameType) {
|
||||||
|
if (remaining <= 0) break;
|
||||||
|
const space = maxStack === Infinity
|
||||||
|
? remaining
|
||||||
|
: Math.min(maxStack - entry.amount, remaining);
|
||||||
|
if (space <= 0) continue;
|
||||||
|
const idx = existing.findIndex((e) => e.id === entry.id);
|
||||||
|
if (idx >= 0) {
|
||||||
|
existing[idx] = { ...entry, amount: entry.amount + space };
|
||||||
|
remaining -= space;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create new entries for remaining amount
|
||||||
|
while (remaining > 0) {
|
||||||
|
let spot: { x: number; y: number } | null = null;
|
||||||
|
for (let y = 0; y <= gridH - eh && !spot; y++) {
|
||||||
|
for (let x = 0; x <= gridW - ew && !spot; x++) {
|
||||||
|
const cells = new Set<string>();
|
||||||
|
for (let dx = 0; dx < ew; dx++) {
|
||||||
|
for (let dy = 0; dy < eh; dy++) {
|
||||||
|
cells.add(`${x + dx},${y + dy}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let overlaps = false;
|
||||||
|
for (const other of existing) {
|
||||||
|
if (other.id == null) continue;
|
||||||
|
const otherRes = other.resource as Resource;
|
||||||
|
if (!otherRes || typeof otherRes === "number") continue;
|
||||||
|
const otherRotated = other.rotated ?? false;
|
||||||
|
const ow = otherRotated ? (otherRes.gridHeight ?? 1) : (otherRes.gridWidth ?? 1);
|
||||||
|
const oh = otherRotated ? (otherRes.gridWidth ?? 1) : (otherRes.gridHeight ?? 1);
|
||||||
|
for (let dx = 0; dx < ow; dx++) {
|
||||||
|
for (let dy = 0; dy < oh; dy++) {
|
||||||
|
if (cells.has(`${other.gridX + dx},${other.gridY + dy}`)) {
|
||||||
|
overlaps = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (overlaps) break;
|
||||||
|
}
|
||||||
|
if (overlaps) break;
|
||||||
|
}
|
||||||
|
if (!overlaps) spot = { x, y };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spot) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: `No space on grid for ${resource.name}. Need ${ew}×${eh} free cells.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackAmount = Math.min(remaining, maxStack === Infinity ? remaining : maxStack);
|
||||||
|
existing.push({ resource: resourceId, amount: stackAmount, gridX: spot.x, gridY: spot.y, rotated: false });
|
||||||
|
remaining -= stackAmount;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
existing.push({ resource: resourceId, amount });
|
// No grid — flat-list behavior
|
||||||
|
const existingIndex = existing.findIndex((e) => {
|
||||||
|
const res = e.resource;
|
||||||
|
return typeof res === "object" && res !== null && "id" in res
|
||||||
|
? res.id === resourceId
|
||||||
|
: res === resourceId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
existing[existingIndex] = {
|
||||||
|
...existing[existingIndex],
|
||||||
|
amount: existing[existingIndex].amount + amount,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
existing.push({ resource: resourceId, amount, gridX: 0, gridY: 0, rotated: false });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = await payload.update({
|
const updated = await payload.update({
|
||||||
|
|
@ -287,3 +370,460 @@ export async function transferResource(
|
||||||
return { success: false, error: message };
|
return { success: false, error: message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Grid-specific actions ---
|
||||||
|
|
||||||
|
interface GridActionResult {
|
||||||
|
success: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function placeResourceOnGrid(
|
||||||
|
structureId: number,
|
||||||
|
resourceId: number,
|
||||||
|
amount: number,
|
||||||
|
gridX: number,
|
||||||
|
gridY: number,
|
||||||
|
rotated: boolean,
|
||||||
|
): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
|
||||||
|
if (amount <= 0) return { success: false, error: "Amount must be greater than zero." };
|
||||||
|
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const structureType = structure.type;
|
||||||
|
|
||||||
|
const isProhibited = structureType.prohibitedStorage?.some((rule) => {
|
||||||
|
if (!rule.item) return false;
|
||||||
|
const item = rule.item;
|
||||||
|
if (typeof item === "object" && "id" in item) {
|
||||||
|
return item.relationTo === "resources" && item.value === resourceId;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (isProhibited) {
|
||||||
|
return { success: false, error: "This resource is prohibited from storage here." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const resource = await payload.findByID({ collection: "resources", id: resourceId, depth: 0 }).catch(() => null);
|
||||||
|
if (!resource) return { success: false, error: "Resource not found." };
|
||||||
|
|
||||||
|
if (resource.stackable && resource.maxStackSize && amount > resource.maxStackSize) {
|
||||||
|
return { success: false, error: `Cannot stack more than ${resource.maxStackSize} units.` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentMass = calculateMass(structure.storedResources);
|
||||||
|
const addedMass = (resource.massPerUnit ?? 0) * amount;
|
||||||
|
const maxMass = structureType.maxCapacityMass;
|
||||||
|
if (maxMass && currentMass + addedMass > maxMass) {
|
||||||
|
return { success: false, error: `Exceeds mass capacity. Available: ${(maxMass - currentMass).toLocaleString()} kg.` };
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridW = structureType.xSize ?? 0;
|
||||||
|
const gridH = structureType.ySize ?? 0;
|
||||||
|
if (!gridW || !gridH) {
|
||||||
|
return { success: false, error: "This structure does not have grid dimensions configured." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const resW = (resource.gridWidth ?? 1);
|
||||||
|
const resH = (resource.gridHeight ?? 1);
|
||||||
|
const effectiveW = rotated ? resH : resW;
|
||||||
|
const effectiveH = rotated ? resW : resH;
|
||||||
|
|
||||||
|
if (gridX < 0 || gridY < 0 || gridX + effectiveW > gridW || gridY + effectiveH > gridH) {
|
||||||
|
return { success: false, error: "Placement is outside grid bounds." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
|
const targetCells = new Set<string>();
|
||||||
|
for (let dx = 0; dx < effectiveW; dx++) {
|
||||||
|
for (let dy = 0; dy < effectiveH; dy++) {
|
||||||
|
targetCells.add(`${gridX + dx},${gridY + dy}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of existing) {
|
||||||
|
if (entry.id == null) continue;
|
||||||
|
const entryRes = entry.resource as Resource;
|
||||||
|
if (!entryRes || typeof entryRes === "number") continue;
|
||||||
|
const entryRotated = entry.rotated ?? false;
|
||||||
|
const ew = entryRotated ? (entryRes.gridHeight ?? 1) : (entryRes.gridWidth ?? 1);
|
||||||
|
const eh = entryRotated ? (entryRes.gridWidth ?? 1) : (entryRes.gridHeight ?? 1);
|
||||||
|
for (let dx = 0; dx < ew; dx++) {
|
||||||
|
for (let dy = 0; dy < eh; dy++) {
|
||||||
|
if (targetCells.has(`${entry.gridX + dx},${entry.gridY + dy}`)) {
|
||||||
|
return { success: false, error: "Placement overlaps with an existing item." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.push({ resource: resourceId, amount, gridX, gridY, rotated });
|
||||||
|
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: existing } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function moveGridItem(
|
||||||
|
structureId: number,
|
||||||
|
entryId: string,
|
||||||
|
gridX: number,
|
||||||
|
gridY: number,
|
||||||
|
rotated?: boolean,
|
||||||
|
): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
|
const idx = existing.findIndex((e) => e.id === entryId);
|
||||||
|
if (idx < 0) return { success: false, error: "Item not found in grid." };
|
||||||
|
|
||||||
|
const entry = existing[idx];
|
||||||
|
const res = entry.resource as Resource;
|
||||||
|
if (!res || typeof res === "number") return { success: false, error: "Invalid resource." };
|
||||||
|
|
||||||
|
const effectiveRotated = rotated ?? entry.rotated ?? false;
|
||||||
|
const ew = effectiveRotated ? (res.gridHeight ?? 1) : (res.gridWidth ?? 1);
|
||||||
|
const eh = effectiveRotated ? (res.gridWidth ?? 1) : (res.gridHeight ?? 1);
|
||||||
|
const gridW = structure.type.xSize ?? 0;
|
||||||
|
const gridH = structure.type.ySize ?? 0;
|
||||||
|
|
||||||
|
if (gridX < 0 || gridY < 0 || gridX + ew > gridW || gridY + eh > gridH) {
|
||||||
|
return { success: false, error: "Placement is outside grid bounds." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetCells = new Set<string>();
|
||||||
|
for (let dx = 0; dx < ew; dx++) {
|
||||||
|
for (let dy = 0; dy < eh; dy++) {
|
||||||
|
targetCells.add(`${gridX + dx},${gridY + dy}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const other of existing) {
|
||||||
|
if (other.id === entryId || other.id == null) continue;
|
||||||
|
const otherRes = other.resource as Resource;
|
||||||
|
if (!otherRes || typeof otherRes === "number") continue;
|
||||||
|
const otherRotated = other.rotated ?? false;
|
||||||
|
const ow = otherRotated ? (otherRes.gridHeight ?? 1) : (otherRes.gridWidth ?? 1);
|
||||||
|
const oh = otherRotated ? (otherRes.gridWidth ?? 1) : (otherRes.gridHeight ?? 1);
|
||||||
|
for (let dx = 0; dx < ow; dx++) {
|
||||||
|
for (let dy = 0; dy < oh; dy++) {
|
||||||
|
if (targetCells.has(`${other.gridX + dx},${other.gridY + dy}`)) {
|
||||||
|
return { success: false, error: "Cannot move here — overlaps with another item." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existing[idx] = rotated !== undefined
|
||||||
|
? { ...entry, gridX, gridY, rotated }
|
||||||
|
: { ...entry, gridX, gridY };
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: existing } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function mergeGridStacks(
|
||||||
|
structureId: number,
|
||||||
|
sourceId: string,
|
||||||
|
targetId: string,
|
||||||
|
): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
|
|
||||||
|
const srcIdx = existing.findIndex((e) => e.id === sourceId);
|
||||||
|
const tgtIdx = existing.findIndex((e) => e.id === targetId);
|
||||||
|
if (srcIdx < 0 || tgtIdx < 0) return { success: false, error: "Item not found." };
|
||||||
|
|
||||||
|
const src = existing[srcIdx];
|
||||||
|
const tgt = existing[tgtIdx];
|
||||||
|
|
||||||
|
const srcRes = src.resource as Resource;
|
||||||
|
const tgtRes = tgt.resource as Resource;
|
||||||
|
if (!srcRes || typeof srcRes === "number" || !tgtRes || typeof tgtRes === "number") {
|
||||||
|
return { success: false, error: "Invalid resource." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcResId = typeof srcRes === "object" ? srcRes.id : srcRes;
|
||||||
|
const tgtResId = typeof tgtRes === "object" ? tgtRes.id : tgtRes;
|
||||||
|
if (srcResId !== tgtResId) return { success: false, error: "Cannot merge different resource types." };
|
||||||
|
|
||||||
|
if (tgtRes.stackable === false) return { success: false, error: "This resource cannot be stacked." };
|
||||||
|
|
||||||
|
const maxStack = tgtRes.maxStackSize;
|
||||||
|
const spaceInTarget = maxStack ? maxStack - tgt.amount : Infinity;
|
||||||
|
if (spaceInTarget <= 0) return { success: false, error: "Target stack is full." };
|
||||||
|
|
||||||
|
const transferAmount = Math.min(src.amount, spaceInTarget);
|
||||||
|
|
||||||
|
existing[tgtIdx] = { ...tgt, amount: tgt.amount + transferAmount };
|
||||||
|
|
||||||
|
if (transferAmount >= src.amount) {
|
||||||
|
existing.splice(srcIdx, 1);
|
||||||
|
} else {
|
||||||
|
existing[srcIdx] = { ...src, amount: src.amount - transferAmount };
|
||||||
|
}
|
||||||
|
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: existing } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rotateGridItem(
|
||||||
|
structureId: number,
|
||||||
|
entryId: string,
|
||||||
|
): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
|
const idx = existing.findIndex((e) => e.id === entryId);
|
||||||
|
if (idx < 0) return { success: false, error: "Item not found in grid." };
|
||||||
|
|
||||||
|
const entry = existing[idx];
|
||||||
|
const res = entry.resource as Resource;
|
||||||
|
if (!res || typeof res === "number") return { success: false, error: "Invalid resource." };
|
||||||
|
if (res.rotatable === false) return { success: false, error: "This resource cannot be rotated." };
|
||||||
|
|
||||||
|
const newRotated = !(entry.rotated ?? false);
|
||||||
|
const ew = newRotated ? (res.gridHeight ?? 1) : (res.gridWidth ?? 1);
|
||||||
|
const eh = newRotated ? (res.gridWidth ?? 1) : (res.gridHeight ?? 1);
|
||||||
|
const gridW = structure.type.xSize ?? 0;
|
||||||
|
const gridH = structure.type.ySize ?? 0;
|
||||||
|
|
||||||
|
if (entry.gridX + ew > gridW || entry.gridY + eh > gridH) {
|
||||||
|
return { success: false, error: "Rotation would push the item out of bounds." };
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetCells = new Set<string>();
|
||||||
|
for (let dx = 0; dx < ew; dx++) {
|
||||||
|
for (let dy = 0; dy < eh; dy++) {
|
||||||
|
targetCells.add(`${entry.gridX + dx},${entry.gridY + dy}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const other of existing) {
|
||||||
|
if (other.id === entryId || other.id == null) continue;
|
||||||
|
const otherRes = other.resource as Resource;
|
||||||
|
if (!otherRes || typeof otherRes === "number") continue;
|
||||||
|
const otherRotated = other.rotated ?? false;
|
||||||
|
const ow = otherRotated ? (otherRes.gridHeight ?? 1) : (otherRes.gridWidth ?? 1);
|
||||||
|
const oh = otherRotated ? (otherRes.gridWidth ?? 1) : (otherRes.gridHeight ?? 1);
|
||||||
|
for (let dx = 0; dx < ow; dx++) {
|
||||||
|
for (let dy = 0; dy < oh; dy++) {
|
||||||
|
if (targetCells.has(`${other.gridX + dx},${other.gridY + dy}`)) {
|
||||||
|
return { success: false, error: "Rotation would overlap with another item." };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existing[idx] = { ...entry, rotated: newRotated };
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: existing } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeGridItem(
|
||||||
|
structureId: number,
|
||||||
|
entryId: string,
|
||||||
|
): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
|
const idx = existing.findIndex((e) => e.id === entryId);
|
||||||
|
if (idx < 0) return { success: false, error: "Item not found in grid." };
|
||||||
|
|
||||||
|
existing.splice(idx, 1);
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: existing } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function splitGridStack(
|
||||||
|
structureId: number,
|
||||||
|
entryId: string,
|
||||||
|
splitAmount: number,
|
||||||
|
): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
|
||||||
|
if (splitAmount <= 0) return { success: false, error: "Split amount must be positive." };
|
||||||
|
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const existing = [...(structure.storedResources ?? [])];
|
||||||
|
const idx = existing.findIndex((e) => e.id === entryId);
|
||||||
|
if (idx < 0) return { success: false, error: "Item not found in grid." };
|
||||||
|
|
||||||
|
const entry = existing[idx];
|
||||||
|
if (splitAmount >= entry.amount) return { success: false, error: "Split amount must be less than the stack size." };
|
||||||
|
|
||||||
|
const res = entry.resource as Resource;
|
||||||
|
if (!res || typeof res === "number") return { success: false, error: "Invalid resource." };
|
||||||
|
|
||||||
|
const rotated = entry.rotated ?? false;
|
||||||
|
const ew = rotated ? (res.gridHeight ?? 1) : (res.gridWidth ?? 1);
|
||||||
|
const eh = rotated ? (res.gridWidth ?? 1) : (res.gridHeight ?? 1);
|
||||||
|
const gridW = structure.type.xSize ?? 0;
|
||||||
|
const gridH = structure.type.ySize ?? 0;
|
||||||
|
|
||||||
|
let placed = false;
|
||||||
|
for (let y = 0; y <= gridH - eh && !placed; y++) {
|
||||||
|
for (let x = 0; x <= gridW - ew && !placed; x++) {
|
||||||
|
const targetCells = new Set<string>();
|
||||||
|
for (let dx = 0; dx < ew; dx++) {
|
||||||
|
for (let dy = 0; dy < eh; dy++) {
|
||||||
|
targetCells.add(`${x + dx},${y + dy}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let overlaps = false;
|
||||||
|
for (const other of existing) {
|
||||||
|
if (other.id === entryId || other.id == null) continue;
|
||||||
|
const otherRes = other.resource as Resource;
|
||||||
|
if (!otherRes || typeof otherRes === "number") continue;
|
||||||
|
const otherRotated = other.rotated ?? false;
|
||||||
|
const ow = otherRotated ? (otherRes.gridHeight ?? 1) : (otherRes.gridWidth ?? 1);
|
||||||
|
const oh = otherRotated ? (otherRes.gridWidth ?? 1) : (otherRes.gridHeight ?? 1);
|
||||||
|
for (let dx = 0; dx < ow; dx++) {
|
||||||
|
for (let dy = 0; dy < oh; dy++) {
|
||||||
|
if (targetCells.has(`${other.gridX + dx},${other.gridY + dy}`)) {
|
||||||
|
overlaps = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (overlaps) break;
|
||||||
|
}
|
||||||
|
if (overlaps) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overlaps) {
|
||||||
|
existing[idx] = { ...entry, amount: entry.amount - splitAmount };
|
||||||
|
existing.push({
|
||||||
|
resource: entry.resource,
|
||||||
|
amount: splitAmount,
|
||||||
|
gridX: x,
|
||||||
|
gridY: y,
|
||||||
|
rotated,
|
||||||
|
});
|
||||||
|
placed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!placed) return { success: false, error: "No empty spot available for the split stack." };
|
||||||
|
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: existing } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function autoArrangeGrid(structureId: number): Promise<GridActionResult> {
|
||||||
|
try {
|
||||||
|
const { payload } = await authenticate();
|
||||||
|
const structure = await getStructure(payload, structureId);
|
||||||
|
const gridW = structure.type.xSize ?? 0;
|
||||||
|
const gridH = structure.type.ySize ?? 0;
|
||||||
|
if (!gridW || !gridH) return { success: false, error: "Structure has no grid dimensions." };
|
||||||
|
|
||||||
|
const items = [...(structure.storedResources ?? [])].map((e) => ({
|
||||||
|
...e,
|
||||||
|
_sortKey: e.gridY * 1000 + e.gridX,
|
||||||
|
}));
|
||||||
|
items.sort((a, b) => (a._sortKey ?? 0) - (b._sortKey ?? 0));
|
||||||
|
|
||||||
|
const placed: typeof items = [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const res = item.resource as Resource;
|
||||||
|
if (!res || typeof res === "number") continue;
|
||||||
|
const rotated = item.rotated ?? false;
|
||||||
|
const ew = rotated ? (res.gridHeight ?? 1) : (res.gridWidth ?? 1);
|
||||||
|
const eh = rotated ? (res.gridWidth ?? 1) : (res.gridHeight ?? 1);
|
||||||
|
|
||||||
|
let spotFound = false;
|
||||||
|
for (let y = 0; y <= gridH - eh && !spotFound; y++) {
|
||||||
|
for (let x = 0; x <= gridW - ew && !spotFound; x++) {
|
||||||
|
const targetCells = new Set<string>();
|
||||||
|
for (let dx = 0; dx < ew; dx++) {
|
||||||
|
for (let dy = 0; dy < eh; dy++) {
|
||||||
|
targetCells.add(`${x + dx},${y + dy}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let overlaps = false;
|
||||||
|
for (const p of placed) {
|
||||||
|
const pRes = p.resource as Resource;
|
||||||
|
if (!pRes || typeof pRes === "number") continue;
|
||||||
|
const pRotated = p.rotated ?? false;
|
||||||
|
const pw = pRotated ? (pRes.gridHeight ?? 1) : (pRes.gridWidth ?? 1);
|
||||||
|
const ph = pRotated ? (pRes.gridWidth ?? 1) : (pRes.gridHeight ?? 1);
|
||||||
|
for (let dx = 0; dx < pw; dx++) {
|
||||||
|
for (let dy = 0; dy < ph; dy++) {
|
||||||
|
if (targetCells.has(`${p.gridX + dx},${p.gridY + dy}`)) {
|
||||||
|
overlaps = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (overlaps) break;
|
||||||
|
}
|
||||||
|
if (overlaps) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!overlaps) {
|
||||||
|
placed.push({ ...item, gridX: x, gridY: y });
|
||||||
|
spotFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spotFound) {
|
||||||
|
placed.push({ ...item, gridX: 0, gridY: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = placed.map(({ _sortKey, ...rest }) => rest);
|
||||||
|
await payload.update({ collection: "game-structures", id: structureId, data: { storedResources: updated } });
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return message === "Unauthorized"
|
||||||
|
? { success: false, error: "You must be logged in." }
|
||||||
|
: { success: false, error: message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue