diff --git a/src/events/character/connect.ts b/src/events/character/connect.ts index 8fd3bdb..837444e 100644 --- a/src/events/character/connect.ts +++ b/src/events/character/connect.ts @@ -3,6 +3,7 @@ import Database from '#application/database' import ZoneManager from '#managers/zoneManager' import CharacterHairRepository from '#repositories/characterHairRepository' import CharacterRepository from '#repositories/characterRepository' +import TeleportService from '#services/teleportService' interface CharacterConnectPayload { characterId: number @@ -59,21 +60,14 @@ export default class CharacterConnectEvent extends BaseEvent { // wait 300 ms, @TODO: Find a better way to do this await new Promise(resolve => setTimeout(resolve, 100)) - const zone = ZoneManager.getZoneById(character.zone!.id) - if (!zone) { - this.logger.error('zone:character:join error: Zone not found') - return - } - - zone.addCharacter(character) - - const zoneCharacter = ZoneManager.getCharacterById(character.id) - if (!zoneCharacter) { - this.logger.error('zone:character:join error: Zone character not found') - return - } - - await zoneCharacter.teleport(character.zone!.id, character.positionX, character.positionY) + await TeleportService.teleportCharacter(character.id, { + targetZoneId: character.zone!.id, + targetX: character.positionX, + targetY: character.positionY, + rotation: character.rotation, + isInitialJoin: true, + character + }) } catch (error) { this.handleError('Failed to connect character', error) } diff --git a/src/events/chat/gameMaster/teleportCommand.ts b/src/events/chat/gameMaster/teleportCommand.ts index 9886938..82039f8 100644 --- a/src/events/chat/gameMaster/teleportCommand.ts +++ b/src/events/chat/gameMaster/teleportCommand.ts @@ -1,9 +1,8 @@ import { BaseEvent } from '#application/base/baseEvent' import ZoneManager from '#managers/zoneManager' -import zoneManager from '#managers/zoneManager' -import ZoneCharacter from '#models/zoneCharacter' import ZoneRepository from '#repositories/zoneRepository' import ChatService from '#services/chatService' +import TeleportService from '#services/teleportService' type TypePayload = { message: string @@ -14,9 +13,8 @@ export default class TeleportCommandEvent extends BaseEvent { this.socket.on('chat:message', this.handleEvent.bind(this)) } - private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise { + private async handleEvent(data: TypePayload, callback: (response: boolean) => void) { try { - // Check if character exists const zoneCharacter = ZoneManager.getCharacterById(this.socket.characterId!) if (!zoneCharacter) { this.logger.error('chat:message error', 'Character not found') @@ -25,7 +23,6 @@ export default class TeleportCommandEvent extends BaseEvent { const character = zoneCharacter.character - // Check if the user is the GM if (character.role !== 'gm') { this.logger.info(`User ${character.id} tried to set time but is not a game master.`) return @@ -35,54 +32,68 @@ export default class TeleportCommandEvent extends BaseEvent { const args = ChatService.getArgs('teleport', data.message) - if (!args || args.length !== 1) { - this.socket.emit('notification', { title: 'Server message', message: 'Usage: /teleport ' }) + if (!args || args.length === 0 || args.length > 3) { + this.socket.emit('notification', { + title: 'Server message', + message: 'Usage: /teleport [x] [y]' + }) return } const zoneId = parseInt(args[0], 10) - if (isNaN(zoneId)) { - this.socket.emit('notification', { title: 'Server message', message: 'Invalid zone ID' }) + const targetX = args[1] ? parseInt(args[1], 10) : 0 + const targetY = args[2] ? parseInt(args[2], 10) : 0 + + if (isNaN(zoneId) || isNaN(targetX) || isNaN(targetY)) { + this.socket.emit('notification', { + title: 'Server message', + message: 'Invalid parameters. All values must be numbers.' + }) return } const zone = await ZoneRepository.getById(zoneId) if (!zone) { - this.socket.emit('notification', { title: 'Server message', message: 'Zone not found' }) + this.socket.emit('notification', { + title: 'Server message', + message: 'Zone not found' + }) return } - if (character.zoneId === zone.id) { - this.socket.emit('notification', { title: 'Server message', message: 'You are already in that zone' }) + if (character.zone.id === zone.id && targetX === character.positionX && targetY === character.positionY) { + this.socket.emit('notification', { + title: 'Server message', + message: 'You are already at that location' + }) return } - // Remove character from current zone - zoneManager.removeCharacter(character.id) - this.io.to(character.zoneId.toString()).emit('zone:character:leave', character.id) - this.socket.leave(character.zoneId.toString()) - - // Add character to new zone - zoneManager.getZoneById(zone.id)?.addCharacter(character) - this.io.to(zone.id.toString()).emit('zone:character:join', character) - this.socket.join(zone.id.toString()) - - character.zoneId = zone.id - character.positionX = 0 - character.positionY = 0 - - zoneCharacter.isMoving = false - - this.socket.emit('zone:character:teleport', { - zone, - characters: ZoneManager.getZoneById(zone.id)?.getCharactersInZone() + const success = await TeleportService.teleportCharacter(character.id, { + targetZoneId: zone.id, + targetX, + targetY, + rotation: character.rotation }) - this.socket.emit('notification', { title: 'Server message', message: `You have been teleported to ${zone.name}` }) - this.logger.info('teleport', `Character ${character.id} teleported to zone ${zone.id}`) + if (!success) { + return this.socket.emit('notification', { + title: 'Server message', + message: 'Failed to teleport' + }) + } + + this.socket.emit('notification', { + title: 'Server message', + message: `Teleported to ${zone.name} (${targetX}, ${targetY})` + }) + this.logger.info('teleport', `Character ${character.id} teleported to zone ${zone.id} at position (${targetX}, ${targetY})`) } catch (error: any) { this.logger.error(`Error in teleport command: ${error.message}`) - this.socket.emit('notification', { title: 'Server message', message: 'An error occurred while teleporting' }) + this.socket.emit('notification', { + title: 'Server message', + message: 'An error occurred while teleporting' + }) } } -} +} \ No newline at end of file diff --git a/src/events/disconnect.ts b/src/events/disconnect.ts index c159361..694ca8f 100644 --- a/src/events/disconnect.ts +++ b/src/events/disconnect.ts @@ -6,7 +6,7 @@ export default class DisconnectEvent extends BaseEvent { this.socket.on('disconnect', this.handleEvent.bind(this)) } - private async handleEvent(data: any): Promise { + private async handleEvent(): Promise { try { if (!this.socket.userId) { this.logger.info('User disconnected but had no user set') @@ -21,20 +21,10 @@ export default class DisconnectEvent extends BaseEvent { return } - const character = zoneCharacter.character - - // Save character position and remove from zone - zoneCharacter.isMoving = false - await zoneCharacter.savePosition() - ZoneManager.removeCharacter(this.socket.characterId!) - + await zoneCharacter.disconnect(this.socket, this.io) this.logger.info('User disconnected along with their character') - - // Inform other clients that the character has left - this.io.in(character.zone!.id.toString()).emit('zone:character:leave', character.id) - this.io.emit('character:disconnect', character.id) } catch (error: any) { this.logger.error('disconnect error: ' + error.message) } } -} +} \ No newline at end of file diff --git a/src/models/zoneCharacter.ts b/src/models/zoneCharacter.ts index 8bfde71..bee68c2 100644 --- a/src/models/zoneCharacter.ts +++ b/src/models/zoneCharacter.ts @@ -1,6 +1,6 @@ import { Character } from '#entities/character' +import TeleportService from '#services/teleportService' import ZoneManager from '#managers/zoneManager' -import Logger, { LoggerType } from '#application/logger' import SocketManager from '#managers/socketManager' class ZoneCharacter { @@ -13,44 +13,44 @@ class ZoneCharacter { } public async savePosition() { - await this.character.setPositionX(this.character.positionX).setPositionY(this.character.positionY).setRotation(this.character.rotation).setZone(this.character.zone).update() + await this.character + .setPositionX(this.character.positionX) + .setPositionY(this.character.positionY) + .setRotation(this.character.rotation) + .setZone(this.character.zone) + .update() } public async teleport(zoneId: number, targetX: number, targetY: number): Promise { - const io = SocketManager.getIO() - const socket = SocketManager.getSocketByCharacterId(this.character.id) - const zone = ZoneManager.getZoneById(zoneId)?.getZone() - const logger = Logger.type(LoggerType.APP) - console.log('teleporting') - - if (!socket) { - logger.error('zone:character:move error: Socket not found') - return - } - - if (!zone) { - logger.error('zone:character:move error: Zone not found') - return - } - - // Let other clients know of new character - io.to(zone.id.toString()).emit('zone:character:join', 'ewaewa') - - // Update zoneCharacter properties - this.currentPath = null - this.isMoving = false - - // Update local character object - await this.character.setPositionX(targetX).setPositionY(targetY).setRotation(this.character.rotation).setZone(zone).update() - - // Emit teleport event - socket.emit('zone:character:teleport', { - zone, - characters: ZoneManager.getZoneById(zone.id)?.getCharactersInZone() + await TeleportService.teleportCharacter(this.character.id, { + targetZoneId: zoneId, + targetX, + targetY }) + } - console.log('teleported') + public async disconnect(socket: Socket, io: Server): Promise { + try { + // Stop any movement and save final position + this.isMoving = false + this.currentPath = null + await this.savePosition() + + // Leave zone and remove from manager + if (this.character.zone) { + socket.leave(this.character.zone.id.toString()) + ZoneManager.removeCharacter(this.character.id) + + // Notify zone players + io.in(this.character.zone.id.toString()).emit('zone:character:leave', this.character.id) + } + + // Notify all players + io.emit('character:disconnect', this.character.id) + } catch (error) { + console.error(`Error disconnecting character ${this.character.id}:`, error) + } } } -export default ZoneCharacter +export default ZoneCharacter \ No newline at end of file diff --git a/src/services/teleportService.ts b/src/services/teleportService.ts new file mode 100644 index 0000000..97d4bea --- /dev/null +++ b/src/services/teleportService.ts @@ -0,0 +1,83 @@ +import { Character } from '#entities/character' +import ZoneManager from '#managers/zoneManager' +import SocketManager from '#managers/socketManager' +import Logger, { LoggerType } from '#application/logger' +import ZoneCharacter from '#models/zoneCharacter' + +interface TeleportOptions { + targetZoneId: number + targetX: number + targetY: number + rotation?: number + isInitialJoin?: boolean + character?: Character +} + +class TeleportService { + private readonly logger = Logger.type(LoggerType.GAME) + + public async teleportCharacter(characterId: number, options: TeleportOptions): Promise { + const { targetZoneId, targetX, targetY, rotation = 0, isInitialJoin = false, character } = options + + const socket = SocketManager.getSocketByCharacterId(characterId) + const targetZone = ZoneManager.getZoneById(targetZoneId) + + if (!socket || !targetZone) { + this.logger.error(`Teleport failed - Missing socket or target zone for character ${characterId}`) + return false + } + + if (isInitialJoin && !character) { + this.logger.error('Initial join requires character data') + return false + } + + const existingCharacter = !isInitialJoin && ZoneManager.getCharacterById(characterId) + const zoneCharacter = isInitialJoin + ? new ZoneCharacter(character!) + : existingCharacter || (() => { + this.logger.error(`Teleport failed - Character ${characterId} not found in ZoneManager`) + return null + })() + + if (!zoneCharacter) return false + + try { + const currentZoneId = zoneCharacter.character.zone?.id + const io = SocketManager.getIO() + + // Handle current zone cleanup + if (currentZoneId) { + socket.leave(currentZoneId.toString()) + ZoneManager.removeCharacter(characterId) + io.in(currentZoneId.toString()).emit('zone:character:leave', characterId) + } + + // Update character position and zone + await zoneCharacter.character + .setPositionX(targetX) + .setPositionY(targetY) + .setRotation(rotation) + .setZone(targetZone.getZone()) + .update() + + // Join new zone + socket.join(targetZoneId.toString()) + targetZone.addCharacter(zoneCharacter.character) + + // Notify clients + io.in(targetZoneId.toString()).emit('zone:character:join', zoneCharacter) + socket.emit('zone:character:teleport', { + zone: targetZone.getZone(), + characters: targetZone.getCharactersInZone() + }) + + return true + } catch (error) { + this.logger.error(`Teleport error for character ${characterId}: ${error}`) + return false + } + } +} + +export default new TeleportService() \ No newline at end of file