import { Server } from 'socket.io' import { TSocket, ExtendedCharacter } from '../../utilities/types' import { CharacterMoveService } from '../../services/character/characterMoveService' import { ZoneEventTileService } from '../../services/zoneEventTileService' import { MovementValidator } from '../../utilities/character/movementValidator' import prisma from '../../utilities/prisma' import { ZoneEventTile, ZoneEventTileTeleport } from '@prisma/client' import Rotation from '../../utilities/character/rotation' import logger from '../../utilities/logger' type ZoneEventTileWithTeleport = ZoneEventTile & { teleport: ZoneEventTileTeleport } export default class CharacterMoveEvent { private characterMoveService: CharacterMoveService private zoneEventTileService: ZoneEventTileService private movementValidator: MovementValidator private nextPath: { [index: number]: { x: number; y: number }[] } = [] constructor( private readonly io: Server, private readonly socket: TSocket ) { this.characterMoveService = new CharacterMoveService() this.zoneEventTileService = new ZoneEventTileService() this.movementValidator = new MovementValidator() } public listen(): void { this.socket.on('character:initMove', this.handleCharacterMove.bind(this)) } private async handleCharacterMove({ positionX, positionY }: { positionX: number; positionY: number }): Promise { const { character } = this.socket if (!character) { logger.error('character:move error', 'Character not found') return } const path = await this.characterMoveService.calculatePath(character, positionX, positionY) if (!path) { this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found') return } if (character.isMoving && !character.resetMovement) { character.resetMovement = true this.nextPath[character.id] = path } else { await this.moveAlongPath(character, path) } } private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise { for (let i = 0; i < path.length - 1; i++) { const start = path[i] const end = path[i + 1] if (!(await this.movementValidator.isValidMove(character, end))) { break } if (character.isMoving && character.resetMovement) { character.isMoving = false character.resetMovement = false const nextPath = this.nextPath[character.id] this.moveAlongPath(character, nextPath) break } if (!character.isMoving) { character.isMoving = true } 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) } }) if (zoneEventTile) { if (zoneEventTile.type === 'BLOCK') { break } if (zoneEventTile.type === 'TELEPORT') { const teleportTile = (await prisma.zoneEventTile.findFirst({ where: { id: zoneEventTile.id }, include: { teleport: true } })) as ZoneEventTileWithTeleport if (teleportTile) { await this.handleZoneEventTile(teleportTile) break } } } await this.characterMoveService.updatePosition(character, end) this.io.in(character.zoneId.toString()).emit('character:move', character) await this.characterMoveService.applyMovementDelay() } this.finalizeMovement(character) } private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise { const { character } = this.socket if (!character) { logger.error('character:move error', 'Character not found') return } const teleport = zoneEventTile.teleport if (teleport) { await this.zoneEventTileService.handleTeleport(this.io, this.socket, character, teleport) return } } private finalizeMovement(character: ExtendedCharacter): void { character.isMoving = false this.io.in(character.zoneId.toString()).emit('character:move', character) } }