From 365d04b67872f93e4036aa3a8c6b58a535bded23 Mon Sep 17 00:00:00 2001 From: Alexander Andreev Date: Sun, 4 Jan 2026 17:31:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=B0=D0=B4=D0=BC=D0=B8=D0=BD=20=D1=82=D1=83=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GEMINI.md | 2 +- app/pages/village.vue | 68 +++++++++++++++- .../admin/village/complete-clearing.post.ts | 51 ++++++++++++ server/api/admin/village/reset.post.ts | 33 ++++++++ server/services/villageService.ts | 77 ++++++++----------- 5 files changed, 184 insertions(+), 47 deletions(-) create mode 100644 server/api/admin/village/complete-clearing.post.ts create mode 100644 server/api/admin/village/reset.post.ts 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

+ + +
- \ 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[] = [];