forked from noxious/server
More movement improvements
This commit is contained in:
parent
cbd6e2c307
commit
17fa2a8f6e
@ -1,7 +1,6 @@
|
|||||||
import { BaseEvent } from '@/application/base/baseEvent'
|
import { BaseEvent } from '@/application/base/baseEvent'
|
||||||
import { SocketEvent } from '@/application/enums'
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { MapEventTileWithTeleport } from '@/application/types'
|
import type { MapEventTileWithTeleport } from '@/application/types'
|
||||||
import type { Character } from '@/entities/character'
|
|
||||||
import MapManager from '@/managers/mapManager'
|
import MapManager from '@/managers/mapManager'
|
||||||
import MapCharacter from '@/models/mapCharacter'
|
import MapCharacter from '@/models/mapCharacter'
|
||||||
import CharacterMoveService from '@/services/characterMoveService'
|
import CharacterMoveService from '@/services/characterMoveService'
|
||||||
@ -9,9 +8,10 @@ import CharacterMoveService from '@/services/characterMoveService'
|
|||||||
export default class CharacterMove extends BaseEvent {
|
export default class CharacterMove extends BaseEvent {
|
||||||
private readonly STEP_DELAY = 100
|
private readonly STEP_DELAY = 100
|
||||||
private readonly THROTTLE_DELAY = 230
|
private readonly THROTTLE_DELAY = 230
|
||||||
private readonly MAX_REQUEST_DISTANCE = 30 // Maximum allowed distance for movement requests
|
private readonly MAX_REQUEST_DISTANCE = 30
|
||||||
private movementTimeout: NodeJS.Timeout | null = null
|
private movementTimeout: NodeJS.Timeout | null = null
|
||||||
private lastKnownPosition: { x: number; y: number } | null = null
|
private lastKnownPosition: { x: number; y: number } | null = null
|
||||||
|
private isProcessingMove = false
|
||||||
|
|
||||||
public listen(): void {
|
public listen(): void {
|
||||||
this.socket.on(SocketEvent.MAP_CHARACTER_MOVE, this.handleEvent.bind(this))
|
this.socket.on(SocketEvent.MAP_CHARACTER_MOVE, this.handleEvent.bind(this))
|
||||||
@ -34,12 +34,15 @@ export default class CharacterMove extends BaseEvent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop any existing movement before starting a new one
|
||||||
|
await this.stopExistingMovement(mapCharacter)
|
||||||
|
|
||||||
// Validate current position against last known position
|
// Validate current position against last known position
|
||||||
const movementValidation = CharacterMoveService.validateMovementDistance(currentX, currentY, this.lastKnownPosition, this.STEP_DELAY, mapCharacter.isMoving)
|
const movementValidation = CharacterMoveService.validateMovementDistance(currentX, currentY, this.lastKnownPosition, this.STEP_DELAY, mapCharacter.isMoving)
|
||||||
|
|
||||||
if (!movementValidation.isValid) {
|
if (!movementValidation.isValid) {
|
||||||
character.setPositionX(this.lastKnownPosition!.x).setPositionY(this.lastKnownPosition!.y)
|
character.setPositionX(this.lastKnownPosition!.x).setPositionY(this.lastKnownPosition!.y)
|
||||||
this.broadcastMovement(character, false)
|
CharacterMoveService.broadcastMovement(character, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,53 +54,59 @@ export default class CharacterMove extends BaseEvent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If character is already moving to the same target position, ignore the request
|
|
||||||
if (mapCharacter.isMoving && mapCharacter.currentPath?.length) {
|
|
||||||
const lastPathPoint = mapCharacter.currentPath[mapCharacter.currentPath.length - 1]
|
|
||||||
if (lastPathPoint && Math.abs(lastPathPoint.positionX - positionX) < 1 && Math.abs(lastPathPoint.positionY - positionY) < 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel any ongoing movement
|
|
||||||
CharacterMoveService.cancelCurrentMovement(mapCharacter)
|
|
||||||
|
|
||||||
// Update last known position
|
|
||||||
this.lastKnownPosition = { x: currentX, y: currentY }
|
|
||||||
|
|
||||||
// Calculate path to target position
|
|
||||||
const path = await CharacterMoveService.calculatePath(character, Math.floor(positionX), Math.floor(positionY))
|
const path = await CharacterMoveService.calculatePath(character, Math.floor(positionX), Math.floor(positionY))
|
||||||
|
|
||||||
if (!path?.length) {
|
if (!path?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start new movement
|
// Set new movement state
|
||||||
mapCharacter.isMoving = true
|
mapCharacter.isMoving = true
|
||||||
mapCharacter.currentPath = path
|
mapCharacter.currentPath = path
|
||||||
|
this.isProcessingMove = true
|
||||||
|
|
||||||
|
// Start the movement
|
||||||
await this.moveAlongPath(mapCharacter)
|
await this.moveAlongPath(mapCharacter)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.error('map:character:move error: ' + error.message)
|
this.logger.error('map:character:move error: ' + error.message)
|
||||||
|
} finally {
|
||||||
|
this.isProcessingMove = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async stopExistingMovement(mapCharacter: MapCharacter): Promise<void> {
|
||||||
|
// Clear existing movement timeout
|
||||||
|
if (this.movementTimeout) {
|
||||||
|
clearTimeout(this.movementTimeout)
|
||||||
|
this.movementTimeout = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for any ongoing movement processing to complete
|
||||||
|
if (this.isProcessingMove) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, this.STEP_DELAY))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only clear the path, keep isMoving state for continuous animation
|
||||||
|
mapCharacter.currentPath = null
|
||||||
|
}
|
||||||
|
|
||||||
private async moveAlongPath(mapCharacter: MapCharacter): Promise<void> {
|
private async moveAlongPath(mapCharacter: MapCharacter): Promise<void> {
|
||||||
const character = mapCharacter.getCharacter()
|
const character = mapCharacter.getCharacter()
|
||||||
const path = mapCharacter.currentPath
|
const path = mapCharacter.currentPath
|
||||||
|
|
||||||
if (!path?.length) return
|
if (!path?.length || !character) return
|
||||||
|
|
||||||
let lastMoveTime = Date.now()
|
let lastMoveTime = Date.now()
|
||||||
let currentTile, nextTile
|
let currentTile, nextTile
|
||||||
|
const movementId = Date.now() // Unique identifier for this movement sequence
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let i = 0; i < path.length - 1; i++) {
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
|
// Check if this movement sequence is still valid
|
||||||
if (!mapCharacter.isMoving || mapCharacter.currentPath !== path) {
|
if (!mapCharacter.isMoving || mapCharacter.currentPath !== path) {
|
||||||
this.broadcastMovement(character, false)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure minimum time between moves using a single Date.now() call
|
|
||||||
const timeSinceLastMove = Date.now() - lastMoveTime
|
const timeSinceLastMove = Date.now() - lastMoveTime
|
||||||
if (timeSinceLastMove < this.STEP_DELAY) {
|
if (timeSinceLastMove < this.STEP_DELAY) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, this.STEP_DELAY - timeSinceLastMove))
|
await new Promise((resolve) => setTimeout(resolve, this.STEP_DELAY - timeSinceLastMove))
|
||||||
@ -110,14 +119,15 @@ export default class CharacterMove extends BaseEvent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update character rotation and position in a single operation
|
// Update character position and rotation
|
||||||
character
|
character
|
||||||
.setRotation(CharacterMoveService.calculateRotation(currentTile.positionX, currentTile.positionY, nextTile.positionX, nextTile.positionY))
|
.setRotation(CharacterMoveService.calculateRotation(currentTile.positionX, currentTile.positionY, nextTile.positionX, nextTile.positionY))
|
||||||
.setPositionX(nextTile.positionX)
|
.setPositionX(nextTile.positionX)
|
||||||
.setPositionY(nextTile.positionY)
|
.setPositionY(nextTile.positionY)
|
||||||
|
|
||||||
// Check for map events at the next tile
|
// Check for map events
|
||||||
const mapEventTile = await CharacterMoveService.checkMapEvents(character.getMap().getId(), nextTile)
|
const mapEventTile = await CharacterMoveService.checkMapEvents(character.getMap().getId(), nextTile)
|
||||||
|
|
||||||
if (mapEventTile) {
|
if (mapEventTile) {
|
||||||
if (mapEventTile.type === 'BLOCK') break
|
if (mapEventTile.type === 'BLOCK') break
|
||||||
if (mapEventTile.type === 'TELEPORT' && mapEventTile.teleport) {
|
if (mapEventTile.type === 'TELEPORT' && mapEventTile.teleport) {
|
||||||
@ -126,17 +136,15 @@ export default class CharacterMove extends BaseEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast movement
|
// Only broadcast if this is still the current movement
|
||||||
if (mapCharacter.isMoving && mapCharacter.currentPath === path) {
|
if (mapCharacter.isMoving && mapCharacter.currentPath === path) {
|
||||||
this.broadcastMovement(character, true)
|
CharacterMoveService.broadcastMovement(character, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply movement delay between steps
|
|
||||||
if (i < path.length - 2) {
|
if (i < path.length - 2) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, this.STEP_DELAY))
|
await new Promise((resolve) => setTimeout(resolve, this.STEP_DELAY))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last known position and move time
|
|
||||||
this.lastKnownPosition = {
|
this.lastKnownPosition = {
|
||||||
x: nextTile.positionX,
|
x: nextTile.positionX,
|
||||||
y: nextTile.positionY
|
y: nextTile.positionY
|
||||||
@ -144,41 +152,31 @@ export default class CharacterMove extends BaseEvent {
|
|||||||
lastMoveTime = Date.now()
|
lastMoveTime = Date.now()
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (mapCharacter.isMoving) {
|
// Only finalize if this movement is still active
|
||||||
this.finalizeMovement(mapCharacter)
|
if (mapCharacter.isMoving && mapCharacter.currentPath === path) {
|
||||||
|
await this.finalizeMovement(mapCharacter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private finalizeMovement(mapCharacter: MapCharacter): void {
|
private async finalizeMovement(mapCharacter: MapCharacter): Promise<void> {
|
||||||
// Clear any existing timeout
|
|
||||||
if (this.movementTimeout) {
|
if (this.movementTimeout) {
|
||||||
clearTimeout(this.movementTimeout)
|
clearTimeout(this.movementTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the current path immediately
|
|
||||||
mapCharacter.currentPath = null
|
mapCharacter.currentPath = null
|
||||||
|
|
||||||
// Set new timeout for movement state cleanup
|
const character = mapCharacter.getCharacter()
|
||||||
this.movementTimeout = setTimeout(() => {
|
if (character) {
|
||||||
// Ensure the character is still in a valid state
|
await mapCharacter.savePosition()
|
||||||
if (!mapCharacter.isMoving || !mapCharacter.currentPath) {
|
|
||||||
mapCharacter.isMoving = false
|
|
||||||
// Save the final position and broadcast it
|
|
||||||
mapCharacter.savePosition().then(() => {
|
|
||||||
this.broadcastMovement(mapCharacter.character, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, this.STEP_DELAY * 2) // Increased delay to ensure all movement processing is complete
|
|
||||||
}
|
|
||||||
|
|
||||||
private broadcastMovement(character: Character, isMoving: boolean): void {
|
// Set a timeout to stop movement animation if no new movement is received
|
||||||
this.io.in(character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVE, {
|
this.movementTimeout = setTimeout(() => {
|
||||||
characterId: character.id,
|
if (!mapCharacter.currentPath) {
|
||||||
positionX: character.getPositionX(),
|
mapCharacter.isMoving = false
|
||||||
positionY: character.getPositionY(),
|
CharacterMoveService.broadcastMovement(character, false)
|
||||||
rotation: character.getRotation(),
|
}
|
||||||
isMoving
|
}, this.THROTTLE_DELAY) // Use THROTTLE_DELAY to match input timing
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { BaseService } from '@/application/base/baseService'
|
import { BaseService } from '@/application/base/baseService'
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { MapEventTileWithTeleport, UUID } from '@/application/types'
|
import type { MapEventTileWithTeleport, UUID } from '@/application/types'
|
||||||
import { Character } from '@/entities/character'
|
import { Character } from '@/entities/character'
|
||||||
import MapManager from '@/managers/mapManager'
|
import MapManager from '@/managers/mapManager'
|
||||||
|
import SocketManager from '@/managers/socketManager'
|
||||||
import MapEventTileRepository from '@/repositories/mapEventTileRepository'
|
import MapEventTileRepository from '@/repositories/mapEventTileRepository'
|
||||||
import TeleportService from '@/services/characterTeleportService'
|
import TeleportService from '@/services/characterTeleportService'
|
||||||
import MapCharacter from "@/models/mapCharacter";
|
import type { Server } from 'socket.io'
|
||||||
|
|
||||||
type Position = { positionX: number; positionY: number }
|
type Position = { positionX: number; positionY: number }
|
||||||
export type Node = Position & { parent?: Node; g: number; h: number; f: number }
|
export type Node = Position & { parent?: Node; g: number; h: number; f: number }
|
||||||
@ -75,6 +77,8 @@ class PriorityQueue<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CharacterMoveService extends BaseService {
|
class CharacterMoveService extends BaseService {
|
||||||
|
private io: Server = SocketManager.getIO()
|
||||||
|
|
||||||
// Rotation lookup table for better performance
|
// Rotation lookup table for better performance
|
||||||
private readonly ROTATION_MAP = {
|
private readonly ROTATION_MAP = {
|
||||||
diagonal: {
|
diagonal: {
|
||||||
@ -277,10 +281,14 @@ class CharacterMoveService extends BaseService {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
public cancelCurrentMovement(mapCharacter: MapCharacter): void {
|
public broadcastMovement(character: Character, isMoving: boolean): void {
|
||||||
if (!mapCharacter.isMoving) return
|
this.io.in(character.map.id).emit(SocketEvent.MAP_CHARACTER_MOVE, {
|
||||||
mapCharacter.isMoving = false
|
characterId: character.id,
|
||||||
mapCharacter.currentPath = null
|
positionX: character.getPositionX(),
|
||||||
|
positionY: character.getPositionY(),
|
||||||
|
rotation: character.getRotation(),
|
||||||
|
isMoving
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public validateMovementDistance(
|
public validateMovementDistance(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user