diff --git a/src/events/map/characterMove.ts b/src/events/map/characterMove.ts index 89b7673..1d8ea36 100644 --- a/src/events/map/characterMove.ts +++ b/src/events/map/characterMove.ts @@ -8,6 +8,8 @@ import TeleportService from '#services/characterTeleportService' export default class CharacterMove extends BaseEvent { private readonly characterService = CharacterService + private readonly MOVEMENT_CANCEL_DELAY = 100 + private movementTimeouts: Map = new Map() public listen(): void { this.socket.on('map:character:move', this.handleEvent.bind(this)) @@ -20,62 +22,94 @@ export default class CharacterMove extends BaseEvent { return } - // If already moving, cancel current movement and wait for it to fully stop + // Clear any existing movement timeout + const existingTimeout = this.movementTimeouts.get(this.socket.characterId!) + if (existingTimeout) { + clearTimeout(existingTimeout) + this.movementTimeouts.delete(this.socket.characterId!) + } + + // If already moving, cancel current movement if (mapCharacter.isMoving) { mapCharacter.isMoving = false mapCharacter.currentPath = null - await new Promise((resolve) => setTimeout(resolve, 10)) + + // Add small delay before starting new movement + await new Promise((resolve) => { + const timeout = setTimeout(resolve, this.MOVEMENT_CANCEL_DELAY) + this.movementTimeouts.set(this.socket.characterId!, timeout) + }) + } + + // Validate target position is within reasonable range + const currentX = mapCharacter.character.positionX + const currentY = mapCharacter.character.positionY + const distance = Math.sqrt(Math.pow(positionX - currentX, 2) + Math.pow(positionY - currentY, 2)) + + if (distance > 20) { + // Maximum allowed distance + this.io.in(mapCharacter.character.map.id).emit('map:character:moveError', 'Target position too far') + return } const path = await this.characterService.calculatePath(mapCharacter.character, positionX, positionY) - if (!path) { + if (!path?.length) { this.io.in(mapCharacter.character.map.id).emit('map:character:moveError', 'No valid path found') return } // Start new movement mapCharacter.isMoving = true - mapCharacter.currentPath = path // Add this property to MapCharacter class + mapCharacter.currentPath = path await this.moveAlongPath(mapCharacter, path) } private async moveAlongPath(mapCharacter: MapCharacter, path: Array<{ positionX: number; positionY: number }>): Promise { const character = mapCharacter.getCharacter() - for (let i = 0; i < path.length - 1; i++) { - if (!mapCharacter.isMoving || mapCharacter.currentPath !== path) { - return + try { + for (let i = 0; i < path.length - 1; i++) { + if (!mapCharacter.isMoving || mapCharacter.currentPath !== path) { + return + } + + const [start, end] = [path[i], path[i + 1]] + + // Validate each step + if (Math.abs(end.positionX - start.positionX) > 1 || Math.abs(end.positionY - start.positionY) > 1) { + this.logger.error('Invalid path step detected') + break + } + + character.setRotation(CharacterService.calculateRotation(start.positionX, start.positionY, end.positionX, end.positionY)) + + const mapEventTileRepository = new MapEventTileRepository() + const mapEventTile = await mapEventTileRepository.getEventTileByMapIdAndPosition(character.getMap().getId(), Math.floor(end.positionX), Math.floor(end.positionY)) + + if (mapEventTile?.type === 'BLOCK') break + if (mapEventTile?.type === 'TELEPORT' && mapEventTile.teleport) { + await this.handleTeleportMapEventTile(mapEventTile as MapEventTileWithTeleport) + return + } + + // Update position first + character.setPositionX(end.positionX).setPositionY(end.positionY) + + // Then emit with the same properties + this.io.in(character.map.id).emit('map:character:move', { + characterId: character.id, + positionX: character.getPositionX(), + positionY: character.getPositionY(), + rotation: character.getRotation(), + isMoving: true + }) + + await this.characterService.applyMovementDelay() } - - const [start, end] = [path[i], path[i + 1]] - character.setRotation(CharacterService.calculateRotation(start.positionX, start.positionY, end.positionX, end.positionY)) - - const mapEventTileRepository = new MapEventTileRepository() - const mapEventTile = await mapEventTileRepository.getEventTileByMapIdAndPosition(character.getMap().getId(), Math.floor(end.positionX), Math.floor(end.positionY)) - - if (mapEventTile?.type === 'BLOCK') break - if (mapEventTile?.type === 'TELEPORT' && mapEventTile.teleport) { - await this.handleTeleportMapEventTile(mapEventTile as MapEventTileWithTeleport) - return + } finally { + if (mapCharacter.isMoving && mapCharacter.currentPath === path) { + this.finalizeMovement(mapCharacter) } - - // Update position first - character.setPositionX(end.positionX).setPositionY(end.positionY) - - // Then emit with the same properties - this.io.in(character.map.id).emit('map:character:move', { - characterId: character.id, - positionX: character.getPositionX(), - positionY: character.getPositionY(), - rotation: character.getRotation(), - isMoving: true - }) - - await this.characterService.applyMovementDelay() - } - - if (mapCharacter.isMoving && mapCharacter.currentPath === path) { - this.finalizeMovement(mapCharacter) } } diff --git a/src/services/characterMoveService.ts b/src/services/characterMoveService.ts index 0e1c8ff..4fddb26 100644 --- a/src/services/characterMoveService.ts +++ b/src/services/characterMoveService.ts @@ -8,6 +8,7 @@ export type Node = Position & { parent?: Node; g: number; h: number; f: number } class CharacterMoveService extends BaseService { private readonly MOVEMENT_DELAY_MS = 260 + private readonly MAX_PATH_LENGTH = 20 // Limit maximum path length private readonly DIRECTIONS = [ { x: 0, y: -1 }, // Up @@ -45,7 +46,21 @@ class CharacterMoveService extends BaseService { return null } - return this.findPath(start, end, grid) + // Add maximum distance check + const directDistance = Math.sqrt(Math.pow(targetX - character.positionX, 2) + Math.pow(targetY - character.positionY, 2)) + + if (directDistance > this.MAX_PATH_LENGTH) { + return null + } + + const path = this.findPath(start, end, grid) + + // Validate path length + if (path.length > this.MAX_PATH_LENGTH) { + return path.slice(0, this.MAX_PATH_LENGTH) + } + + return path } public calculateRotation(X1: number, Y1: number, X2: number, Y2: number): number {