import Logger, { LoggerType } from '#application/logger' import { 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' interface TeleportOptions { targetMapId: UUID targetX: number targetY: number rotation?: number isInitialJoin?: boolean character?: Character } class TeleportService { private readonly logger = Logger.type(LoggerType.GAME) public async teleportCharacter(characterId: UUID, options: TeleportOptions): Promise { const mapRepository = new MapRepository() 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 false } if (options.isInitialJoin && !options.character) { this.logger.error('Initial join requires character data') return false } const existingCharacter = !options.isInitialJoin && MapManager.getCharacterById(characterId) const mapCharacter = options.isInitialJoin ? new MapCharacter(options.character!) : existingCharacter || (() => { this.logger.error(`Teleport failed - Character ${characterId} not found in MapManager`) return null })() if (!mapCharacter) return false try { const currentMapId = mapCharacter.character.map?.id const io = SocketManager.getIO() // Update character position and map await mapCharacter .getCharacter() .setPositionX(options.targetX) .setPositionY(options.targetY) .setRotation(options.rotation ?? 0) .setMap(targetMap.getMap()) .save() // If the current map is the target map and we are not joining, send move event if (currentMapId === options.targetMapId && !options.isInitialJoin) { // If the current map is the target map, send move event io.in(currentMapId).emit('map:character:move', { characterId: mapCharacter.character.id, positionX: options.targetX, positionY: options.targetY, rotation: options.rotation ?? 0, isMoving: false }) return true } // Handle current map cleanup if (currentMapId) { socket.leave(currentMapId) MapManager.removeCharacter(characterId) io.in(currentMapId).emit('map:character:leave', characterId) } // Join new map socket.join(options.targetMapId) targetMap.addCharacter(mapCharacter.getCharacter()) const map = await mapRepository.getById(options.targetMapId) await mapRepository.getEntityManager().populate(map!, mapRepository.POPULATE_TELEPORT as any) // Notify clients io.in(options.targetMapId).emit('map:character:join', mapCharacter) socket.emit('map:character:teleport', { mapId: options.targetMapId, characters: targetMap.getCharactersInMap() }) return true } catch (error) { this.logger.error(`Teleport error for character ${characterId}: ${error}`) return false } } } export default new TeleportService()