строительство работает в окне деревне. не работает мув, делит, есть баги, когда дровосек рубит много деревьев сразу

This commit is contained in:
Alexander Andreev 2026-01-04 16:49:13 +03:00
parent 3e35a25601
commit 4b83e8339f
7 changed files with 315 additions and 6 deletions

View File

@ -33,12 +33,14 @@ To run the development server with hot-reloading:
```bash ```bash
npm run dev npm run dev
``` ```
Не запускай npm run dev сам. Скажи пользователю, что бы сделал это сам.
### Building for Production ### Building for Production
To build the application for production: To build the application for production:
```bash ```bash
npm run build npm run build
``` ```
Не запускай npm run build сам. Скажи пользователю, что бы сделал это сам.
## 3. Development Conventions ## 3. Development Conventions

View File

@ -35,7 +35,7 @@
<div class="actions-list"> <div class="actions-list">
<div v-for="(action, index) in selectedTile.availableActions" :key="index" class="action-item"> <div v-for="(action, index) in selectedTile.availableActions" :key="index" class="action-item">
<button <button
:disabled="!action.isEnabled" :disabled="!action.isEnabled || isSubmitting"
@click="handleActionClick(action)" @click="handleActionClick(action)"
> >
{{ getActionLabel(action) }} {{ getActionLabel(action) }}
@ -91,9 +91,37 @@ const getActionLabel = (action) => {
return action.type; return action.type;
}; };
const handleActionClick = (action) => { const isSubmitting = ref(false);
console.log('Action clicked:', action);
// In a future task, this will dispatch the action to the backend. const handleActionClick = async (action) => {
if (isSubmitting.value) return;
isSubmitting.value = true;
try {
const response = await useFetch('/api/village/action', {
method: 'POST',
body: {
tileId: selectedTile.value.id,
actionType: action.type,
payload: {
...(action.type === 'BUILD' && { buildingType: action.buildingType }),
...(action.type === 'MOVE' && { toTileId: action.toTileId }), // Assuming action.toTileId will be present for MOVE
},
},
});
if (response.error.value) {
alert(response.error.value.data?.statusMessage || 'An unknown error occurred.');
} else {
villageData.value = response.data.value;
selectedTile.value = null;
}
} catch (e) {
console.error('Failed to perform action:', e);
alert('An unexpected error occurred. Please check the console.');
} finally {
isSubmitting.value = false;
}
}; };
</script> </script>

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "dummy" TEXT;

View File

@ -0,0 +1,28 @@
/*
Warnings:
- You are about to drop the column `dummy` on the `User` table. All the data in the column will be lost.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_User" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"nickname" TEXT,
"avatar" TEXT DEFAULT '/avatars/default.png',
"coins" INTEGER NOT NULL DEFAULT 0,
"exp" INTEGER NOT NULL DEFAULT 0,
"soundOn" BOOLEAN NOT NULL DEFAULT true,
"confettiOn" BOOLEAN NOT NULL DEFAULT true,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_User" ("avatar", "coins", "confettiOn", "createdAt", "email", "exp", "id", "nickname", "password", "soundOn", "updatedAt") SELECT "avatar", "coins", "confettiOn", "createdAt", "email", "exp", "id", "nickname", "password", "soundOn", "updatedAt" FROM "User";
DROP TABLE "User";
ALTER TABLE "new_User" RENAME TO "User";
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@ -14,6 +14,9 @@ datasource db {
enum VillageObjectType { enum VillageObjectType {
HOUSE HOUSE
FIELD FIELD
LUMBERJACK
QUARRY
WELL
} }
enum TerrainType { enum TerrainType {

View File

@ -0,0 +1,45 @@
// server/api/village/action.post.ts
import { getUserIdFromSession } from '../../utils/auth';
import { buildOnTile, clearTile, moveObject, removeObject } from '../../services/villageService';
import { getVillageState } from '../../services/villageService';
export default defineEventHandler(async (event) => {
const userId = await getUserIdFromSession(event);
const body = await readBody(event);
const { tileId, actionType, payload } = body;
if (!tileId || !actionType) {
throw createError({ statusCode: 400, statusMessage: 'Missing tileId or actionType' });
}
switch (actionType) {
case 'BUILD':
if (!payload?.buildingType) {
throw createError({ statusCode: 400, statusMessage: 'Missing buildingType for BUILD action' });
}
await buildOnTile(userId, tileId, payload.buildingType);
break;
case 'CLEAR':
await clearTile(userId, tileId);
break;
case 'MOVE':
if (!payload?.toTileId) {
throw createError({ statusCode: 400, statusMessage: 'Missing toTileId for MOVE action' });
}
await moveObject(userId, tileId, payload.toTileId);
break;
case 'REMOVE':
await removeObject(userId, tileId);
break;
default:
throw createError({ statusCode: 400, statusMessage: 'Invalid actionType' });
}
// Return the full updated village state
return getVillageState(userId);
});

View File

@ -296,4 +296,205 @@ export async function getVillageState(userId: number): Promise<FullVillage> {
}); });
return { ...finalVillageState, tiles: tilesWithActions } as any; return { ...finalVillageState, tiles: tilesWithActions } as any;
} }
// --- Action Service Functions ---
export async function buildOnTile(userId: number, tileId: number, buildingType: string) {
const { VillageObjectType } = await import('@prisma/client');
const validBuildingTypes = Object.keys(VillageObjectType);
if (!validBuildingTypes.includes(buildingType)) {
throw createError({ statusCode: 400, statusMessage: `Invalid building type: ${buildingType}` });
}
return prisma.$transaction(async (tx) => {
// 1. Fetch all necessary data
const user = await tx.user.findUniqueOrThrow({ where: { id: userId } });
const tile = await tx.villageTile.findUniqueOrThrow({ where: { id: tileId }, include: { village: true } });
// Ownership check
if (tile.village.userId !== userId) {
throw createError({ statusCode: 403, statusMessage: "You don't own this tile" });
}
// Business logic validation
if (tile.terrainType !== 'EMPTY' || tile.object) {
throw createError({ statusCode: 400, statusMessage: 'Tile is not empty' });
}
const cost = BUILDING_COSTS[buildingType];
if (user.coins < cost) {
throw createError({ statusCode: 400, statusMessage: 'Not enough coins' });
}
if (PRODUCING_BUILDINGS.includes(buildingType)) {
const villageObjects = await tx.villageObject.findMany({ where: { villageId: tile.villageId } });
const housesCount = villageObjects.filter(o => o.type === 'HOUSE').length;
const producingCount = villageObjects.filter(o => PRODUCING_BUILDINGS.includes(o.type)).length;
if (producingCount >= housesCount) {
throw createError({ statusCode: 400, statusMessage: 'Not enough workers (houses)' });
}
}
// 2. Perform mutations
await tx.user.update({
where: { id: userId },
data: { coins: { decrement: cost } },
});
await tx.villageObject.create({
data: {
type: buildingType as keyof typeof VillageObjectType,
villageId: tile.villageId,
tileId: tileId,
},
});
});
}
export async function clearTile(userId: number, tileId: number) {
return prisma.$transaction(async (tx) => {
const tile = await tx.villageTile.findUniqueOrThrow({
where: { id: tileId },
include: { village: { include: { objects: true } } },
});
if (tile.village.userId !== userId) {
throw createError({ statusCode: 403, statusMessage: "You don't own this tile" });
}
if (tile.terrainState !== 'IDLE') {
throw createError({ statusCode: 400, statusMessage: 'Tile is not idle' });
}
if (tile.terrainType === 'BLOCKED_TREE') {
const hasLumberjack = tile.village.objects.some(o => o.type === 'LUMBERJACK');
if (!hasLumberjack) throw createError({ statusCode: 400, statusMessage: 'Requires a Lumberjack to clear trees' });
} else if (tile.terrainType === 'BLOCKED_STONE') {
const hasQuarry = tile.village.objects.some(o => o.type === 'QUARRY');
if (!hasQuarry) throw createError({ statusCode: 400, statusMessage: 'Requires a Quarry to clear stones' });
} else {
throw createError({ statusCode: 400, statusMessage: 'Tile is not blocked by trees or stones' });
}
await tx.villageTile.update({
where: { id: tileId },
data: {
terrainState: 'CLEARING',
clearingStartedAt: new Date(),
},
});
});
}
export async function removeObject(userId: number, tileId: number) {
// As requested, this is a stub for now.
throw createError({ statusCode: 501, statusMessage: 'Remove action not implemented yet' });
}
export async function moveObject(userId: number, fromTileId: number, toTileId: number) {
// As requested, this is a stub for now.
throw createError({ statusCode: 501, statusMessage: 'Move action not implemented yet' });
}