diff --git a/src/application/base/baseEvent.ts b/src/application/base/baseEvent.ts index 892aaae..e54a1f9 100644 --- a/src/application/base/baseEvent.ts +++ b/src/application/base/baseEvent.ts @@ -8,12 +8,26 @@ import CharacterRepository from '#repositories/characterRepository' export abstract class BaseEvent { protected readonly logger = Logger.type(LoggerType.GAME) + private lastActionTimes: Map = new Map() constructor( readonly io: Server, readonly socket: TSocket ) {} + protected isThrottled(actionId: string, throttleTime: number): boolean { + const now = Date.now() + const lastActionTime = this.lastActionTimes.get(actionId) || 0 + + if (now - lastActionTime < throttleTime) { + return true + } + + this.lastActionTimes.set(actionId, now) + + return false + } + protected async getCharacter(): Promise { const characterRepository = new CharacterRepository() return characterRepository.getById(this.socket.characterId!) diff --git a/src/events/map/characterMove.ts b/src/events/map/characterMove.ts index 42f7c51..efd82cc 100644 --- a/src/events/map/characterMove.ts +++ b/src/events/map/characterMove.ts @@ -9,10 +9,7 @@ import TeleportService from '#services/characterTeleportService' export default class CharacterMove extends BaseEvent { private readonly characterService = CharacterService - private readonly MOVEMENT_CANCEL_DELAY = 250 - private readonly MOVEMENT_THROTTLE = 80 // Minimum time between movement requests - private movementTimeouts: Map = new Map() - private lastMovementTime: Map = new Map() // Track last movement time for each character + private readonly MOVEMENT_THROTTLE = 230 // Minimum time between movement requests public listen(): void { this.socket.on('map:character:move', this.handleEvent.bind(this)) @@ -25,30 +22,22 @@ export default class CharacterMove extends BaseEvent { return } - // Implement request throttling - const now = Date.now() - const lastMove = this.lastMovementTime.get(this.socket.characterId!) || 0 - if (now - lastMove < this.MOVEMENT_THROTTLE) return - - this.lastMovementTime.set(this.socket.characterId!, now) - - // Clear any existing movement timeout - const existingTimeout = this.movementTimeouts.get(this.socket.characterId!) - if (existingTimeout) { - clearTimeout(existingTimeout) - this.movementTimeouts.delete(this.socket.characterId!) + 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) + } + return } // If already moving, cancel current movement - if (mapCharacter.isMoving) { + if (mapCharacter.isMoving && mapCharacter.currentPath && mapCharacter.currentPath.length > 2) { mapCharacter.isMoving = false mapCharacter.currentPath = null - - // 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 @@ -56,12 +45,6 @@ export default class CharacterMove extends BaseEvent { 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?.length) { this.io.in(mapCharacter.character.map.id).emit('map:character:moveError', 'No valid path found') @@ -90,6 +73,10 @@ export default class CharacterMove extends BaseEvent { 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') diff --git a/src/managers/dateManager.ts b/src/managers/dateManager.ts index a4c229b..9892f53 100644 --- a/src/managers/dateManager.ts +++ b/src/managers/dateManager.ts @@ -8,7 +8,7 @@ import WorldRepository from '#repositories/worldRepository' class DateManager { private static readonly CONFIG = { GAME_SPEED: 8, // 24 game hours / 3 real hours - UPDATE_INTERVAL: 1000, // 1 minute + UPDATE_INTERVAL: 1000 // 1 minute } as const private readonly logger = Logger.type(LoggerType.APP) diff --git a/src/services/characterMoveService.ts b/src/services/characterMoveService.ts index a444bdc..49dbc8c 100644 --- a/src/services/characterMoveService.ts +++ b/src/services/characterMoveService.ts @@ -7,8 +7,7 @@ type Position = { positionX: number; positionY: number } export type Node = Position & { parent?: Node; g: number; h: number; f: number } class CharacterMoveService extends BaseService { - private readonly MOVEMENT_DELAY_MS = 200 - private readonly MAX_PATH_LENGTH = 20 // Limit maximum path length + private readonly MOVEMENT_DELAY_MS = 90 private readonly DIRECTIONS = [ { x: 0, y: -1 }, // Up @@ -46,21 +45,7 @@ class CharacterMoveService extends BaseService { return null } - // 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 + return this.findPath(start, end, grid) } public calculateRotation(X1: number, Y1: number, X2: number, Y2: number): number {