import { ZoneEventTileWithTeleport } from '#application/types' import ZoneManager from '#managers/zoneManager' import ZoneCharacter from '#models/zoneCharacter' import zoneEventTileRepository from '#repositories/zoneEventTileRepository' import CharacterService from '#services/characterService' import ZoneEventTileService from '#services/zoneEventTileService' import { BaseEvent } from '#application/base/baseEvent' export default class CharacterMove extends BaseEvent { private readonly characterService = CharacterService private readonly zoneEventTileService = ZoneEventTileService public listen(): void { this.socket.on('character:move', this.handleEvent.bind(this)) } private async handleEvent({ positionX, positionY }: { positionX: number; positionY: number }): Promise { const zoneCharacter = ZoneManager.getCharacterById(this.socket.characterId!) if (!zoneCharacter?.character) { this.logger.error('character:move error: Character not found or not initialized') return } // If already moving, cancel current movement and wait for it to fully stop if (zoneCharacter.isMoving) { zoneCharacter.isMoving = false await new Promise((resolve) => setTimeout(resolve, 100)) } const path = await this.characterService.calculatePath(zoneCharacter.character, positionX, positionY) if (!path) { this.io.in(zoneCharacter.character.zone!.id.toString()).emit('character:moveError', 'No valid path found') return } // Start new movement zoneCharacter.isMoving = true zoneCharacter.currentPath = path // Add this property to ZoneCharacter class await this.moveAlongPath(zoneCharacter, 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++) { if (!zoneCharacter.isMoving || zoneCharacter.currentPath !== path) { return } const [start, end] = [path[i], path[i + 1]] character.rotation = CharacterService.calculateRotation(start.x, start.y, end.x, end.y) const zoneEventTile = await zoneEventTileRepository.getEventTileByZoneIdAndPosition(character.zone!.id, Math.floor(end.x), Math.floor(end.y)) if (zoneEventTile?.type === 'BLOCK') break if (zoneEventTile?.type === 'TELEPORT' && zoneEventTile.teleport) { await this.handleZoneEventTile(zoneEventTile as ZoneEventTileWithTeleport) break } // Update position first character.positionX = end.x character.positionY = end.y // Then emit with the same properties this.io.in(character.zone!.id.toString()).emit('character:move', { id: character.id, positionX: character.positionX, positionY: character.positionY, rotation: character.rotation, isMoving: true }) await this.characterService.applyMovementDelay() } if (zoneCharacter.isMoving && zoneCharacter.currentPath === path) { this.finalizeMovement(zoneCharacter) } } private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise { const zoneCharacter = ZoneManager.getCharacterById(this.socket.characterId!) if (!zoneCharacter) { this.logger.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.zone!.id.toString()).emit('character:move', { id: zoneCharacter.character.id, positionX: zoneCharacter.character.positionX, positionY: zoneCharacter.character.positionY, rotation: zoneCharacter.character.rotation, isMoving: false }) } }