import { Server } from 'socket.io' import { TSocket } from '../../utilities/Types' import ZoneManager from '../../managers/ZoneManager' import prisma from '../../utilities/Prisma' import { AStar, type Node } from '../../utilities/Player/AStar' import Rotation from '../../utilities/Player/Rotation' import { ExtendedCharacter as Character } from '../../utilities/Types' interface SocketResponse { position_x: number position_y: number } const characterMoveTokens = new Map() export default function setupCharacterMove(socket: TSocket, io: Server) { socket.on('character:move', async (data: SocketResponse) => { try { console.log('character:move requested', data) if (!socket.character) { console.error('character:move error', 'Character not found') return } const oldMoveToken = characterMoveTokens.get(socket.character.id) if (oldMoveToken) { characterMoveTokens.delete(socket.character.id) } const moveToken = Symbol('moveToken') characterMoveTokens.set(socket.character.id, moveToken) const grid = await ZoneManager.getGrid(socket.character.zoneId) if (!grid || grid.length === 0) { console.error('character:move error', 'Grid not found or empty') return } const start = { x: Math.floor(socket.character.position_x), y: Math.floor(socket.character.position_y) } const end = { x: Math.floor(data.position_x), y: Math.floor(data.position_y) } console.log('Pathfinding from', start, 'to', end) console.log('Grid dimensions:', grid.length, 'x', grid[0].length) // Check if the destination is an obstacle if (isObstacle(end, grid)) { console.log('character:move error', 'Destination is an obstacle') socket.emit('character:moveError', 'Destination is an obstacle') return } const path = AStar.findPath(start, end, grid) if (path.length > 0) { socket.character.isMoving = true io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character) moveAlongPath(socket, io, path, grid, moveToken).catch(console.error) } else { console.log('character:move error', 'No valid path found') socket.emit('character:moveError', 'No valid path found') } } catch (error) { console.error('character:move error', error) if (socket.character) { socket.character.isMoving = false io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character) } } }) } async function moveAlongPath(socket: TSocket, io: Server, path: Node[], grid: number[][], moveToken: Symbol) { if (!socket.character) return const totalSteps = path.length const updateInterval = 50 // milliseconds between updates for (let step = 0; step < totalSteps - 1; step++) { if (characterMoveTokens.get(socket.character.id) !== moveToken) { console.log('Movement cancelled for character', socket.character.id) return } const startTime = Date.now() const stepDuration = 250 // 250ms per tile while (Date.now() - startTime < stepDuration) { if (characterMoveTokens.get(socket.character.id) !== moveToken) { console.log('Movement cancelled for character', socket.character.id) return } const progress = (Date.now() - startTime) / stepDuration const currentPosition = interpolatePosition(path[step], path[step + 1], progress) if (isObstacle(currentPosition, grid)) { console.log('Obstacle encountered at', currentPosition) break } const rotation = Rotation.calculate(path[step].x, path[step].y, path[step + 1].x, path[step + 1].y) await updateCharacterPosition(socket.character, currentPosition.x, currentPosition.y, rotation) ZoneManager.updateCharacterInZone(socket.character.zoneId, socket.character) io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character) await new Promise(resolve => setTimeout(resolve, updateInterval)) } } if (socket.character && characterMoveTokens.get(socket.character.id) === moveToken) { socket.character.isMoving = false characterMoveTokens.delete(socket.character.id) io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character) } } function interpolatePosition(start: Node, end: Node, progress: number): Node { return { f: 0, g: 0, h: 0, x: start.x + (end.x - start.x) * progress, y: start.y + (end.y - start.y) * progress } } function isObstacle(position: { x: number; y: number }, grid: number[][]): boolean { const x = Math.floor(position.x) const y = Math.floor(position.y) return grid[y] && grid[y][x] === 1 } async function updateCharacterPosition(character: Character, x: number, y: number, rotation: number) { character.position_x = x character.position_y = y character.rotation = rotation await prisma.character.update({ where: { id: character.id }, data: { position_x: x, position_y: y, rotation } }) }