import { Server } from 'socket.io' import { TSocket, ZoneEventTileWithTeleport } from '../../utilities/types' import { CharacterMoveService } from '../../services/character/characterMoveService' import { ZoneEventTileService } from '../../services/zoneEventTileService' import prisma from '../../utilities/prisma' import Rotation from '../../utilities/character/rotation' import { gameLogger } from '../../utilities/logger' import ZoneManager from '../../managers/zoneManager' import ZoneCharacter from '../../models/zoneCharacter' export default class CharacterMove { private readonly characterMoveService = new CharacterMoveService() private readonly zoneEventTileService = new ZoneEventTileService() private nextPath = new Map() constructor( private readonly io: Server, private readonly socket: TSocket ) {} public listen(): void { this.socket.on('character:move', this.handleCharacterMove.bind(this)) } private async handleCharacterMove({ positionX, positionY }: { positionX: number; positionY: number }): Promise { const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!) if (!zoneCharacter?.character) { gameLogger.error('character:move error', 'Character not found or not initialized') return } const path = await this.characterMoveService.calculatePath(zoneCharacter.character, positionX, positionY) if (!path) { this.io.in(zoneCharacter.character.zoneId.toString()).emit('character:moveError', 'No valid path found') return } if (!zoneCharacter.isMoving) { zoneCharacter.isMoving = true await this.moveAlongPath(zoneCharacter, path) } else { this.nextPath.set(zoneCharacter.character.id, path) } } private async moveAlongPath(zoneCharacter: ZoneCharacter, path: Array<{ x: number; y: number }>): Promise { const { character } = zoneCharacter for (let i = 0; i < path.length - 1; i++) { const [start, end] = [path[i], path[i + 1]] character.rotation = Rotation.calculate(start.x, start.y, end.x, end.y) const zoneEventTile = await prisma.zoneEventTile.findFirst({ where: { zoneId: character.zoneId, positionX: Math.floor(end.x), positionY: Math.floor(end.y) }, include: { teleport: true } }) if (zoneEventTile?.type === 'BLOCK') break if (zoneEventTile?.type === 'TELEPORT' && zoneEventTile.teleport) { await this.handleZoneEventTile(zoneEventTile as ZoneEventTileWithTeleport) break } this.characterMoveService.updatePosition(character, end) this.io.in(character.zoneId.toString()).emit('character:move', zoneCharacter) await this.characterMoveService.applyMovementDelay() } const nextPath = this.nextPath.get(character.id) if (nextPath) { this.nextPath.delete(character.id) await this.moveAlongPath(zoneCharacter, nextPath) } else { this.finalizeMovement(zoneCharacter) } } private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise { const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!) if (!zoneCharacter) { gameLogger.error('character:move error', 'Character not found') return } if (zoneEventTile.teleport) { await this.zoneEventTileService.handleTeleport(this.io, this.socket, zoneCharacter.character, zoneEventTile.teleport) } } private finalizeMovement(zoneCharacter: ZoneCharacter): void { zoneCharacter.isMoving = false this.io.in(zoneCharacter.character.zoneId.toString()).emit('character:move', zoneCharacter) } }