import { SocketEvent } from '@/application/enums' import Logger, { LoggerType } from '@/application/logger' import type { UUID } from '@/application/types' import { Character } from '@/entities/character' import MapManager from '@/managers/mapManager' import SocketManager from '@/managers/socketManager' import MapCharacter from '@/models/mapCharacter' import MapRepository from '@/repositories/mapRepository' import CharacterMoveService from '@/services/characterMoveService' interface TeleportOptions { targetMapId: UUID targetX: number targetY: number rotation?: number isInitialJoin?: boolean character?: Character } class CharacterTeleportService { private readonly logger = Logger.type(LoggerType.GAME) public async teleportCharacter(characterId: UUID, options: TeleportOptions): Promise { try { const { socket, targetMap, mapCharacter } = await this.validateTeleportRequest(characterId, options) if (!socket || !targetMap || !mapCharacter) return false const currentMapId = mapCharacter.character.map.id const currentMap = MapManager.getMapById(currentMapId) const io = SocketManager.getIO() // Update character position and map await this.updateCharacterPosition(mapCharacter, options, targetMap) // Handle same map teleport if (currentMapId === options.targetMapId && !options.isInitialJoin) { CharacterMoveService.broadcastMovement(mapCharacter.character, false) return true } // Handle map transition await this.handleMapTransition(socket, io, currentMapId, currentMap, options.targetMapId, targetMap, characterId, mapCharacter) return true } catch (error) { this.logger.error(`Teleport error for character ${characterId}: ${error}`) return false } } private async validateTeleportRequest(characterId: UUID, options: TeleportOptions) { const socket = SocketManager.getSocketByCharacterId(characterId) const targetMap = MapManager.getMapById(options.targetMapId) if (!socket || !targetMap) { this.logger.error(`Teleport failed - Missing socket or target map for character ${characterId}`) return { socket: null, targetMap: null, mapCharacter: null } } if (options.isInitialJoin && !options.character) { this.logger.error('Initial join requires character data') return { socket, targetMap, mapCharacter: null } } const existingCharacter = !options.isInitialJoin && MapManager.getCharacterById(characterId) const mapCharacter = options.isInitialJoin ? new MapCharacter(options.character!) : existingCharacter || null if (!mapCharacter) { this.logger.error(`Teleport failed - Character ${characterId} not found in MapManager`) } return { socket, targetMap, mapCharacter } } private async updateCharacterPosition(mapCharacter: MapCharacter, options: TeleportOptions, targetMap: any) { await mapCharacter .getCharacter() .setPositionX(options.targetX) .setPositionY(options.targetY) .setRotation(options.rotation ?? 0) .setMap(targetMap.getMap()) .save() } private async handleMapTransition(socket: any, io: any, currentMapId: UUID | undefined, currentMap: any, targetMapId: UUID, targetMap: any, characterId: UUID, mapCharacter: MapCharacter) { // Clean up current map if (currentMapId && currentMap) { socket.leave(currentMapId) await currentMap.removeCharacter(characterId) io.in(currentMapId).emit(SocketEvent.MAP_CHARACTER_LEAVE, characterId) } // Join new map socket.join(targetMapId) targetMap.addCharacter(mapCharacter.getCharacter()) const mapRepository = new MapRepository() const map = await mapRepository.getById(targetMapId) await mapRepository.getEntityManager().populate(map!, mapRepository.POPULATE_TELEPORT as any) // Notify clients io.in(targetMapId).emit(SocketEvent.MAP_CHARACTER_JOIN, mapCharacter) socket.emit(SocketEvent.MAP_CHARACTER_TELEPORT, { mapId: targetMapId, characters: targetMap.getCharactersInMap() }) } } export default new CharacterTeleportService()