Walk improvements

This commit is contained in:
Dennis Postma 2025-02-06 13:47:02 +01:00
parent ba96ae7dd4
commit 765a0468bc
2 changed files with 86 additions and 37 deletions

View File

@ -8,6 +8,8 @@ import TeleportService from '#services/characterTeleportService'
export default class CharacterMove extends BaseEvent { export default class CharacterMove extends BaseEvent {
private readonly characterService = CharacterService private readonly characterService = CharacterService
private readonly MOVEMENT_CANCEL_DELAY = 100
private movementTimeouts: Map<string, NodeJS.Timeout> = new Map()
public listen(): void { public listen(): void {
this.socket.on('map:character:move', this.handleEvent.bind(this)) this.socket.on('map:character:move', this.handleEvent.bind(this))
@ -20,62 +22,94 @@ export default class CharacterMove extends BaseEvent {
return 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) { if (mapCharacter.isMoving) {
mapCharacter.isMoving = false mapCharacter.isMoving = false
mapCharacter.currentPath = null 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) 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') this.io.in(mapCharacter.character.map.id).emit('map:character:moveError', 'No valid path found')
return return
} }
// Start new movement // Start new movement
mapCharacter.isMoving = true mapCharacter.isMoving = true
mapCharacter.currentPath = path // Add this property to MapCharacter class mapCharacter.currentPath = path
await this.moveAlongPath(mapCharacter, path) await this.moveAlongPath(mapCharacter, path)
} }
private async moveAlongPath(mapCharacter: MapCharacter, path: Array<{ positionX: number; positionY: number }>): Promise<void> { private async moveAlongPath(mapCharacter: MapCharacter, path: Array<{ positionX: number; positionY: number }>): Promise<void> {
const character = mapCharacter.getCharacter() const character = mapCharacter.getCharacter()
for (let i = 0; i < path.length - 1; i++) { try {
if (!mapCharacter.isMoving || mapCharacter.currentPath !== path) { for (let i = 0; i < path.length - 1; i++) {
return 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()
} }
} finally {
const [start, end] = [path[i], path[i + 1]] if (mapCharacter.isMoving && mapCharacter.currentPath === path) {
character.setRotation(CharacterService.calculateRotation(start.positionX, start.positionY, end.positionX, end.positionY)) this.finalizeMovement(mapCharacter)
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()
}
if (mapCharacter.isMoving && mapCharacter.currentPath === path) {
this.finalizeMovement(mapCharacter)
} }
} }

View File

@ -8,6 +8,7 @@ export type Node = Position & { parent?: Node; g: number; h: number; f: number }
class CharacterMoveService extends BaseService { class CharacterMoveService extends BaseService {
private readonly MOVEMENT_DELAY_MS = 260 private readonly MOVEMENT_DELAY_MS = 260
private readonly MAX_PATH_LENGTH = 20 // Limit maximum path length
private readonly DIRECTIONS = [ private readonly DIRECTIONS = [
{ x: 0, y: -1 }, // Up { x: 0, y: -1 }, // Up
@ -45,7 +46,21 @@ class CharacterMoveService extends BaseService {
return null 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 { public calculateRotation(X1: number, Y1: number, X2: number, Y2: number): number {