diff --git a/GEMINI.md b/GEMINI.md
index 4b3a167..c050634 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -33,7 +33,7 @@ To run the development server with hot-reloading:
```bash
npm run dev
```
-Не запускай npm run dev сам. Скажи пользователю, что бы сделал это сам.
+Никогда НЕ запускай npm run dev сам. Скажи пользователю, что бы сделал это сам.
### Building for Production
To build the application for production:
diff --git a/app/pages/village.vue b/app/pages/village.vue
index 7b28a42..ec0787c 100644
--- a/app/pages/village.vue
+++ b/app/pages/village.vue
@@ -47,13 +47,20 @@
+
+
+
+
Admin Tools
+ Reset Village
+ Complete All Clearing
+
-
\ No newline at end of file
diff --git a/server/api/admin/village/complete-clearing.post.ts b/server/api/admin/village/complete-clearing.post.ts
new file mode 100644
index 0000000..a8e7f1a
--- /dev/null
+++ b/server/api/admin/village/complete-clearing.post.ts
@@ -0,0 +1,51 @@
+// server/api/admin/village/complete-clearing.post.ts
+import { getUserIdFromSession } from '../../../utils/auth';
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ const userId = await getUserIdFromSession(event);
+
+ // Simple admin check
+ if (userId !== 1) {
+ throw createError({ statusCode: 403, statusMessage: 'Forbidden' });
+ }
+
+ const village = await prisma.village.findUniqueOrThrow({ where: { userId } });
+
+ const tilesToComplete = await prisma.villageTile.findMany({
+ where: {
+ villageId: village.id,
+ terrainState: 'CLEARING',
+ },
+ });
+
+ if (tilesToComplete.length === 0) {
+ return { success: true, message: 'No clearing tasks to complete.' };
+ }
+
+ await prisma.$transaction([
+ // Complete the tiles
+ prisma.villageTile.updateMany({
+ where: {
+ id: { in: tilesToComplete.map(t => t.id) },
+ },
+ data: {
+ terrainState: 'IDLE',
+ terrainType: 'EMPTY',
+ clearingStartedAt: null,
+ },
+ }),
+ // Give the user the rewards (1 coin and 1 exp per tile)
+ prisma.user.update({
+ where: { id: userId },
+ data: {
+ coins: { increment: tilesToComplete.length },
+ exp: { increment: tilesToComplete.length },
+ },
+ }),
+ ]);
+
+ return { success: true, message: `Completed ${tilesToComplete.length} clearing tasks.` };
+});
diff --git a/server/api/admin/village/reset.post.ts b/server/api/admin/village/reset.post.ts
new file mode 100644
index 0000000..24af783
--- /dev/null
+++ b/server/api/admin/village/reset.post.ts
@@ -0,0 +1,33 @@
+// server/api/admin/village/reset.post.ts
+import { getUserIdFromSession } from '../../../utils/auth';
+import { generateVillageForUser } from '../../../services/villageService';
+import { PrismaClient } from '@prisma/client';
+
+const prisma = new PrismaClient();
+
+export default defineEventHandler(async (event) => {
+ const userId = await getUserIdFromSession(event);
+
+ // Simple admin check
+ if (userId !== 1) {
+ throw createError({ statusCode: 403, statusMessage: 'Forbidden' });
+ }
+
+ const user = await prisma.user.findUniqueOrThrow({ where: { id: userId } });
+ const village = await prisma.village.findUnique({ where: { userId } });
+
+ if (village) {
+ await prisma.$transaction([
+ // Note: Order matters due to foreign key constraints.
+ // Delete objects first, then tiles.
+ prisma.villageObject.deleteMany({ where: { villageId: village.id } }),
+ prisma.villageTile.deleteMany({ where: { villageId: village.id } }),
+ prisma.village.delete({ where: { id: village.id } }),
+ ]);
+ }
+
+ // Generate a fresh village
+ await generateVillageForUser(user);
+
+ return { success: true, message: 'Village has been reset.' };
+});
diff --git a/server/services/villageService.ts b/server/services/villageService.ts
index d290632..8e27781 100644
--- a/server/services/villageService.ts
+++ b/server/services/villageService.ts
@@ -166,58 +166,43 @@ export async function getVillageState(userId: number): Promise {
villageSnapshot = await prisma.village.findUnique({ where: { userId }, include: { user: true, tiles: { include: { object: true } }, objects: { include: { tile: true } } } })!;
// --- Step 6: Auto-start Terrain Cleaning ---
- const housesCount = villageSnapshot.objects.filter(o => o.type === 'HOUSE').length;
- const producingCount = villageSnapshot.objects.filter(o => PRODUCING_BUILDINGS.includes(o.type)).length;
- const freeWorkers = housesCount - producingCount;
+ const lumberjackCount = villageSnapshot.objects.filter(o => o.type === 'LUMBERJACK').length;
+ const quarryCount = villageSnapshot.objects.filter(o => o.type === 'QUARRY').length;
- if (producingCount <= housesCount) {
- const manhattanDistance = (p1: {x: number, y: number}, p2: {x: number, y: number}) => Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
- const getDirectionalPriority = (worker: VillageTile, target: VillageTile): number => {
- const dy = target.y - worker.y;
- const dx = target.x - worker.x;
- if (dy < 0) return 1; // Up
- if (dx < 0) return 2; // Left
- if (dx > 0) return 3; // Right
- if (dy > 0) return 4; // Down
- return 5;
- };
+ const clearingTreesCount = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_TREE' && t.terrainState === 'CLEARING').length;
+ const clearingStonesCount = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'CLEARING').length;
- const assignTasks = (workers: (VillageObject & { tile: VillageTile })[], targets: VillageTile[], newlyTargeted: Set) => {
- workers.forEach(worker => {
- const potentialTargets = targets
- .filter(t => !newlyTargeted.has(t.id))
- .map(target => ({ target, distance: manhattanDistance(worker.tile, target) }))
- .sort((a, b) => a.distance - b.distance);
+ const freeLumberjacks = lumberjackCount - clearingTreesCount;
+ const freeQuarries = quarryCount - clearingStonesCount;
- if (!potentialTargets.length) return;
+ const tileIdsToClear = new Set();
- const minDistance = potentialTargets[0].distance;
- const tiedTargets = potentialTargets.filter(t => t.distance === minDistance).map(t => t.target);
-
- tiedTargets.sort((a, b) => getDirectionalPriority(worker.tile, a) - getDirectionalPriority(worker.tile, b));
-
- const bestTarget = tiedTargets[0];
- if (bestTarget) newlyTargeted.add(bestTarget.id);
- });
- };
-
- const lumberjacks = villageSnapshot.objects.filter(obj => obj.type === 'LUMBERJACK') as (VillageObject & { tile: VillageTile })[];
- const quarries = villageSnapshot.objects.filter(obj => obj.type === 'QUARRY') as (VillageObject & { tile: VillageTile })[];
+ if (freeLumberjacks > 0) {
const idleTrees = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_TREE' && t.terrainState === 'IDLE');
- const idleStones = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'IDLE');
- const tileIdsToClear = new Set();
-
- assignTasks(lumberjacks, idleTrees, tileIdsToClear);
- assignTasks(quarries, idleStones, tileIdsToClear);
-
- if (tileIdsToClear.size > 0) {
- await prisma.villageTile.updateMany({
- where: { id: { in: Array.from(tileIdsToClear) } },
- data: { terrainState: 'CLEARING', clearingStartedAt: now },
- });
+ if (idleTrees.length > 0) {
+ // For simplicity, just take the first N available trees. A more complex distance-based heuristic could go here.
+ idleTrees.slice(0, freeLumberjacks).forEach(t => tileIdsToClear.add(t.id));
}
}
+ if (freeQuarries > 0) {
+ const idleStones = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'IDLE');
+ if (idleStones.length > 0) {
+ // For simplicity, just take the first N available stones.
+ idleStones.slice(0, freeQuarries).forEach(t => tileIdsToClear.add(t.id));
+ }
+ }
+
+ if (tileIdsToClear.size > 0) {
+ await prisma.villageTile.updateMany({
+ where: { id: { in: Array.from(tileIdsToClear) } },
+ data: { terrainState: 'CLEARING', clearingStartedAt: now },
+ });
+
+ // Refetch state after starting new clearings
+ villageSnapshot = await prisma.village.findUnique({ where: { userId }, include: { user: true, tiles: { include: { object: true } }, objects: { include: { tile: true } } } })!;
+ }
+
// --- Step 7: Final Fetch & Action Enrichment ---
const finalVillageState = await prisma.village.findUnique({
where: { userId },
@@ -236,6 +221,10 @@ export async function getVillageState(userId: number): Promise {
const { user } = finalVillageState;
const hasLumberjack = finalVillageState.objects.some(o => o.type === 'LUMBERJACK');
const hasQuarry = finalVillageState.objects.some(o => o.type === 'QUARRY');
+
+ const housesCount = finalVillageState.objects.filter(o => o.type === 'HOUSE').length;
+ const producingCount = finalVillageState.objects.filter(o => PRODUCING_BUILDINGS.includes(o.type)).length;
+ const freeWorkers = housesCount - producingCount;
const tilesWithActions = finalVillageState.tiles.map(tile => {
const availableActions: any[] = [];