diff --git a/src/application/base/baseEvent.ts b/src/application/base/baseEvent.ts index e24ade1..84d6bc9 100644 --- a/src/application/base/baseEvent.ts +++ b/src/application/base/baseEvent.ts @@ -28,6 +28,7 @@ export abstract class BaseEvent { } protected async getCharacter(): Promise { + if (!this.socket.characterId) return null const characterRepository = new CharacterRepository() return characterRepository.getById(this.socket.characterId) } diff --git a/src/events/disconnect.ts b/src/events/disconnect.ts index 2d109cd..f570ce3 100644 --- a/src/events/disconnect.ts +++ b/src/events/disconnect.ts @@ -4,7 +4,7 @@ import MapManager from '@/managers/mapManager' export default class DisconnectEvent extends BaseEvent { public listen(): void { - this.socket.on(SocketEvent.DISCONNECT, this.handleEvent.bind(this)) + this.socket.on('disconnect', this.handleEvent.bind(this)) } private async handleEvent(): Promise { @@ -16,7 +16,7 @@ export default class DisconnectEvent extends BaseEvent { this.io.emit(SocketEvent.USER_DISCONNECT, this.socket.userId) - const mapCharacter = MapManager.getCharacterById(this.socket.characterId) + const mapCharacter = MapManager.getCharacterById(this.socket.characterId!) if (!mapCharacter) { this.logger.info('User disconnected but had no character set') return diff --git a/src/events/map/characterMove.ts b/src/events/map/characterMove.ts index ec1649d..203c411 100644 --- a/src/events/map/characterMove.ts +++ b/src/events/map/characterMove.ts @@ -9,54 +9,59 @@ import TeleportService from '@/services/characterTeleportService' export default class CharacterMove extends BaseEvent { private readonly characterService = CharacterService - private readonly MOVEMENT_THROTTLE = 230 // Minimum time between movement requests + private readonly STEP_DELAY = 150 // Milliseconds between each tile movement + private readonly THROTTLE_DELAY = 230 // Minimum time between movement requests + private movementTimeout: NodeJS.Timeout | null = null public listen(): void { this.socket.on(SocketEvent.MAP_CHARACTER_MOVE, this.handleEvent.bind(this)) } private async handleEvent({ positionX, positionY }: { positionX: number; positionY: number }): Promise { - const mapCharacter = MapManager.getCharacterById(this.socket.characterId) - if (!mapCharacter?.getCharacter()) { - this.logger.error('map:character:move error: Character not found or not initialized') - return - } - - if (this.isThrottled(`movement_${this.socket.characterId}`, this.MOVEMENT_THROTTLE)) { - // Only cancel current movement if the new target is different - if (mapCharacter.isMoving && (Math.floor(positionX) !== Math.floor(mapCharacter.character.positionX) || Math.floor(positionY) !== Math.floor(mapCharacter.character.positionY))) { - mapCharacter.isMoving = false - mapCharacter.currentPath = null - // this.finalizeMovement(mapCharacter) + try { + const mapCharacter = MapManager.getCharacterById(this.socket.characterId!) + if (!mapCharacter?.getCharacter()) { + this.logger.error('map:character:move error: Character not found or not initialized') + return } - return - } - // If already moving, cancel current movement - if (mapCharacter.isMoving && mapCharacter.currentPath && mapCharacter.currentPath.length > 2) { + // Cancel any ongoing movement + this.cancelCurrentMovement(mapCharacter) + + // Throttle rapid movement requests + if (this.isThrottled(`movement_${this.socket.characterId}`, this.THROTTLE_DELAY)) { + return + } + + // Calculate path to target position + const path = await this.characterService.calculatePath(mapCharacter.character, Math.floor(positionX), Math.floor(positionY)) + + if (!path?.length) { + this.io.in(mapCharacter.character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVEERROR, 'No valid path found') + return + } + + // Start new movement + mapCharacter.isMoving = true + mapCharacter.currentPath = path + await this.moveAlongPath(mapCharacter) + } catch (error: any) { + this.logger.error('map:character:move error: ' + error.message) + } + } + + private cancelCurrentMovement(mapCharacter: MapCharacter): void { + if (mapCharacter.isMoving) { mapCharacter.isMoving = false mapCharacter.currentPath = null } - - // 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)) - - const path = await this.characterService.calculatePath(mapCharacter.character, positionX, positionY) - if (!path?.length) { - this.io.in(mapCharacter.character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVEERROR, 'No valid path found') - return - } - - // Start new movement - mapCharacter.isMoving = true - mapCharacter.currentPath = path - await this.moveAlongPath(mapCharacter, path) } - private async moveAlongPath(mapCharacter: MapCharacter, path: Array<{ positionX: number; positionY: number }>): Promise { + private async moveAlongPath(mapCharacter: MapCharacter): Promise { const character = mapCharacter.getCharacter() + const path = mapCharacter.currentPath + + if (!path?.length) return try { for (let i = 0; i < path.length - 1; i++) { @@ -64,47 +69,42 @@ export default class CharacterMove extends BaseEvent { return } - const [start, end] = [path[i], path[i + 1]] + const [currentTile, nextTile] = [path[i], path[i + 1]] - if (!start || !end) { - this.logger.error('Invalid path step detected') + if (!currentTile || !nextTile) { + this.logger.error('Invalid movement step detected') break } - if (i !== 0) { - await this.characterService.applyMovementDelay() - } - - // 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') + // Validate step + if (!this.isValidStep(currentTile, nextTile)) { + this.logger.error('Invalid movement step detected') break } - character.setRotation(CharacterService.calculateRotation(start.positionX, start.positionY, end.positionX, end.positionY)) + // Update character rotation + character.setRotation(CharacterService.calculateRotation(currentTile.positionX, currentTile.positionY, nextTile.positionX, nextTile.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 + // Check for map events at the next tile + const mapEventTile = await this.checkMapEvents(character, nextTile) + if (mapEventTile) { + 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) + // Move character to next tile + character.setPositionX(nextTile.positionX).setPositionY(nextTile.positionY) - // Then emit with the same properties - this.io.in(character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVE, { - characterId: character.id, - positionX: character.getPositionX(), - positionY: character.getPositionY(), - rotation: character.getRotation(), - isMoving: true - }) + // Broadcast movement + this.broadcastMovement(character, true) - await this.characterService.applyMovementDelay() + // Apply movement delay between steps + await new Promise((resolve) => setTimeout(resolve, this.STEP_DELAY)) + if (i < path.length - 2) { + } } } finally { if (mapCharacter.isMoving && mapCharacter.currentPath === path) { @@ -113,25 +113,52 @@ export default class CharacterMove extends BaseEvent { } } + private isValidStep(current: { positionX: number; positionY: number }, next: { positionX: number; positionY: number }): boolean { + return Math.abs(next.positionX - current.positionX) <= 1 && Math.abs(next.positionY - current.positionY) <= 1 + } + + private async checkMapEvents(character: any, nextTile: { positionX: number; positionY: number }) { + const mapEventTileRepository = new MapEventTileRepository() + return mapEventTileRepository.getEventTileByMapIdAndPosition(character.getMap().getId(), Math.floor(nextTile.positionX), Math.floor(nextTile.positionY)) + } + private async handleTeleportMapEventTile(mapEventTile: MapEventTileWithTeleport): Promise { - if (mapEventTile.getTeleport()) { - await TeleportService.teleportCharacter(this.socket.characterId, { - targetMapId: mapEventTile.getTeleport()!.getToMap().getId(), - targetX: mapEventTile.getTeleport()!.getToPositionX(), - targetY: mapEventTile.getTeleport()!.getToPositionY(), - rotation: mapEventTile.getTeleport()!.getToRotation() + const teleport = mapEventTile.getTeleport() + if (teleport) { + await TeleportService.teleportCharacter(this.socket.characterId!, { + targetMapId: teleport.getToMap().getId(), + targetX: teleport.getToPositionX(), + targetY: teleport.getToPositionY(), + rotation: teleport.getToRotation() }) } } - private finalizeMovement(mapCharacter: MapCharacter): void { - mapCharacter.isMoving = false - this.io.in(mapCharacter.character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVE, { - characterId: mapCharacter.character.id, - positionX: mapCharacter.character.positionX, - positionY: mapCharacter.character.positionY, - rotation: mapCharacter.character.rotation, - isMoving: mapCharacter.isMoving + private broadcastMovement(character: any, isMoving: boolean): void { + this.io.in(character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVE, { + characterId: character.id, + positionX: character.getPositionX(), + positionY: character.getPositionY(), + rotation: character.getRotation(), + isMoving }) } + + private finalizeMovement(mapCharacter: MapCharacter): void { + // Clear any existing timeout + if (this.movementTimeout) { + clearTimeout(this.movementTimeout) + } + + // Set new timeout + this.movementTimeout = setTimeout(() => { + // Only finalize if there are no pending movements + if (mapCharacter.currentPath === null) { + mapCharacter.isMoving = false + this.broadcastMovement(mapCharacter.character, false) + } + }, this.STEP_DELAY) + + mapCharacter.currentPath = null + } }