добавлен админ тул
This commit is contained in:
parent
4b83e8339f
commit
365d04b678
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -47,13 +47,20 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Panel -->
|
||||
<div v-if="villageData?.user?.id === 1" class="admin-panel">
|
||||
<h3>Admin Tools</h3>
|
||||
<button @click="handleResetVillage" :disabled="isSubmittingAdminAction">Reset Village</button>
|
||||
<button @click="handleCompleteClearing" :disabled="isSubmittingAdminAction">Complete All Clearing</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const { data: villageData, pending, error } = await useFetch('/api/village', {
|
||||
const { data: villageData, pending, error, refresh: refreshVillageData } = await useFetch('/api/village', {
|
||||
lazy: true,
|
||||
server: false, // Ensure this runs on the client-side
|
||||
});
|
||||
|
|
@ -123,6 +130,30 @@ const handleActionClick = async (action) => {
|
|||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const isSubmittingAdminAction = ref(false);
|
||||
|
||||
async function handleAdminAction(url: string) {
|
||||
if (isSubmittingAdminAction.value) return;
|
||||
isSubmittingAdminAction.value = true;
|
||||
|
||||
try {
|
||||
const { error } = await useFetch(url, { method: 'POST' });
|
||||
if (error.value) {
|
||||
alert(error.value.data?.statusMessage || 'An admin action failed.');
|
||||
} else {
|
||||
await refreshVillageData();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to perform admin action:', e);
|
||||
alert('An unexpected error occurred.');
|
||||
} finally {
|
||||
isSubmittingAdminAction.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const handleResetVillage = () => handleAdminAction('/api/admin/village/reset');
|
||||
const handleCompleteClearing = () => handleAdminAction('/api/admin/village/complete-clearing');
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -291,4 +322,37 @@ const handleActionClick = async (action) => {
|
|||
.close-overlay-button:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
.admin-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
border: 2px dashed #dc3545;
|
||||
border-radius: 10px;
|
||||
max-width: 350px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.admin-panel h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.admin-panel button {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.admin-panel button:disabled {
|
||||
background-color: #e9ecef;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
51
server/api/admin/village/complete-clearing.post.ts
Normal file
51
server/api/admin/village/complete-clearing.post.ts
Normal file
|
|
@ -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.` };
|
||||
});
|
||||
33
server/api/admin/village/reset.post.ts
Normal file
33
server/api/admin/village/reset.post.ts
Normal file
|
|
@ -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.' };
|
||||
});
|
||||
|
|
@ -166,58 +166,43 @@ export async function getVillageState(userId: number): Promise<FullVillage> {
|
|||
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<number>) => {
|
||||
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<number>();
|
||||
|
||||
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<number>();
|
||||
|
||||
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<FullVillage> {
|
|||
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[] = [];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user