diff --git a/src/events/character/move.ts b/src/events/character/move.ts index 0dba817..88f3ce2 100644 --- a/src/events/character/move.ts +++ b/src/events/character/move.ts @@ -1,93 +1,156 @@ -import { Server } from 'socket.io' -import { TSocket, ExtendedCharacter } from '../../utilities/types' -import ZoneManager from '../../managers/zoneManager' -import prisma from '../../utilities/prisma' -import { AStar } from '../../utilities/player/aStar' -import Rotation from '../../utilities/player/rotation' +import { Server } from 'socket.io'; +import { TSocket, ExtendedCharacter } from '../../utilities/types'; +import ZoneManager from '../../managers/zoneManager'; +import prisma from '../../utilities/prisma'; +import { AStar } from '../../utilities/player/aStar'; +import Rotation from '../../utilities/player/rotation'; +import ZoneRepository from '../../repositories/zoneRepository'; +import { Character } from '@prisma/client'; -const moveTokens = new Map() +const moveTokens = new Map(); export default function setupCharacterMove(socket: TSocket, io: Server) { - socket.on('character:move', async ({ positionX, positionY }: { positionX: number; positionY: number }) => { - const { character } = socket - if (!character) return console.error('character:move error', 'Character not found') - - moveTokens.set(character.id, Symbol('moveToken')) - const grid = await ZoneManager.getGrid(character.zoneId) - if (!grid?.length) return console.error('character:move error', 'Grid not found or empty') - - const start = { x: Math.floor(character.positionX), y: Math.floor(character.positionY) } - const end = { x: Math.floor(positionX), y: Math.floor(positionY) } - - if (isObstacle(end, grid)) return socket.emit('character:moveError', 'Destination is an obstacle') - - const path = AStar.findPath(start, end, grid) - if (!path.length) return socket.emit('character:moveError', 'No valid path found') - - character.isMoving = true - io.in(character.zoneId.toString()).emit('character:moved', character) - moveAlongPath(socket, io, path, grid).catch(console.error) - }) + socket.on('character:initMove', handleCharacterMove(socket, io)); } -async function moveAlongPath(socket: TSocket, io: Server, path: Array<{ x: number; y: number }>, grid: number[][]) { - const { character } = socket - if (!character) return +const handleCharacterMove = (socket: TSocket, io: Server) => async ({ positionX, positionY }: { positionX: number; positionY: number }) => { + const { character } = socket; + if (!character) return console.error('character:move error', 'Character not found'); - const moveToken = moveTokens.get(character.id) - const stepDuration = 250 - const updateInterval = 50 + const grid = await ZoneManager.getGrid(character.zoneId); + if (!grid?.length) return console.error('character:move error', 'Grid not found or empty'); + + const start = { x: Math.floor(character.positionX), y: Math.floor(character.positionY) }; + const end = { x: Math.floor(positionX), y: Math.floor(positionY) }; + + if (isObstacle(end, grid)) return socket.emit('character:moveError', 'Destination is an obstacle'); + + const path = AStar.findPath(start, end, grid); + if (!path.length) return socket.emit('character:moveError', 'No valid path found'); + + moveTokens.set(character.id, Symbol('moveToken')); + character.isMoving = true; + io.in(character.zoneId.toString()).emit('character:move', character); + moveAlongPath(socket, io, path, grid).catch(console.error); +}; + +async function moveAlongPath(socket: TSocket, io: Server, path: Array<{ x: number; y: number }>, grid: number[][]) { + const { character } = socket; + if (!character) return; + + const moveToken = moveTokens.get(character.id); + const stepDuration = 250; + const updateInterval = 50; for (let i = 0; i < path.length - 1; i++) { - const startTime = Date.now() - const start = path[i] - const end = path[i + 1] + const startTime = Date.now(); + const start = path[i]; + const end = path[i + 1]; while (Date.now() - startTime < stepDuration) { - if (moveTokens.get(character.id) !== moveToken) return + if (moveTokens.get(character.id) !== moveToken) return; - const progress = (Date.now() - startTime) / stepDuration - const current = interpolatePosition(start, end, progress) + const progress = (Date.now() - startTime) / stepDuration; + const current = interpolatePosition(start, end, progress); if (isObstacle(current, grid)) { - // If obstacle encountered, stop at the last valid position - await updateCharacter(character, start, Rotation.calculate(start.x, start.y, end.x, end.y)) - io.in(character.zoneId.toString()).emit('character:moved', character) - return + await updateCharacterPosition(character, start, Rotation.calculate(start.x, start.y, end.x, end.y), socket, io); + return; } - await updateCharacter(character, current, Rotation.calculate(start.x, start.y, end.x, end.y)) - io.in(character.zoneId.toString()).emit('character:moved', character) - await new Promise((resolve) => setTimeout(resolve, updateInterval)) + const tp = await prisma.zoneEventTile.findFirst({ + where: { zoneId: character.zoneId, type: 'TELEPORT', positionX: current.x, positionY: current.y }, + include: { teleport: true } + }); + + if (tp?.teleport) { + await handleTeleport(socket, io, character, tp.teleport, start, end); + return; + } + + await updateCharacterPosition(character, current, Rotation.calculate(start.x, start.y, end.x, end.y), socket, io); + await new Promise(resolve => setTimeout(resolve, updateInterval)); } } - // Ensure the character reaches the exact final position if (moveTokens.get(character.id) === moveToken) { - const finalPosition = path[path.length - 1] - await updateCharacter(character, finalPosition, character.rotation) - character.isMoving = false - moveTokens.delete(character.id) - io.in(character.zoneId.toString()).emit('character:moved', character) + await updateCharacterPosition(character, path[path.length - 1], character.rotation, socket, io); + character.isMoving = false; + moveTokens.delete(character.id); } } +async function handleTeleport(socket: TSocket, io: Server, character: ExtendedCharacter, teleport: any, start: { x: number; y: number }, end: { x: number; y: number }) { + if (teleport.toZoneId === character.zoneId) return; + + const zone = await ZoneRepository.getById(teleport.toZoneId); + if (!zone) return; + + character.isMoving = false; + character.zoneId = teleport.toZoneId; + + moveTokens.delete(character.id); + + socket.leave(character.zoneId.toString()); + socket.join(teleport.toZoneId.toString()); + + await updateCharacterPosition( + character, + { x: teleport.toPositionX, y: teleport.toPositionY }, + Rotation.calculate(start.x, start.y, end.x, end.y), + socket, + io, + teleport.toZoneId + ); +} + +async function updateCharacterPosition( + character: ExtendedCharacter, + position: { x: number; y: number }, + rotation: number, + socket: TSocket, + io: Server, + newZoneId?: number +) { + const oldZoneId = character.zoneId; + + Object.assign(character, { + positionX: position.x, + positionY: position.y, + rotation, + zoneId: newZoneId || character.zoneId + }); + + if (newZoneId && newZoneId !== oldZoneId) { + io.to(oldZoneId.toString()).emit('zone:character:leave', character); + io.to(newZoneId.toString()).emit('zone:character:join', character); + ZoneManager.removeCharacterFromZone(oldZoneId, character as Character); + ZoneManager.addCharacterToZone(newZoneId, character as Character); + } else { + ZoneManager.updateCharacterInZone(character.zoneId, character); + } + + await prisma.character.update({ + where: { id: character.id }, + data: { + positionX: position.x, + positionY: position.y, + rotation, + zoneId: character.zoneId + } + }); + + io.in(character.zoneId.toString()).emit('character:move', character); + socket.emit('character:dataUpdated', character); +} + const interpolatePosition = (start: { x: number; y: number }, end: { x: number; y: number }, progress: number) => ({ x: start.x + (end.x - start.x) * progress, y: start.y + (end.y - start.y) * progress -}) +}); const isObstacle = ({ x, y }: { x: number; y: number }, grid: number[][]) => { - const gridX = Math.floor(x) - const gridY = Math.floor(y) - return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(x)] === 1 || grid[Math.ceil(y)]?.[gridX] === 1 || grid[Math.ceil(y)]?.[Math.ceil(x)] === 1 -} - -async function updateCharacter(character: ExtendedCharacter, { x, y }: { x: number; y: number }, rotation: number) { - Object.assign(character, { positionX: x, positionY: y, rotation }) - ZoneManager.updateCharacterInZone(character.zoneId, character) - await prisma.character.update({ - where: { id: character.id }, - data: { positionX: x, positionY: y, rotation } - }) -} + const gridX = Math.floor(x); + const gridY = Math.floor(y); + return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(x)] === 1 || grid[Math.ceil(y)]?.[gridX] === 1 || grid[Math.ceil(y)]?.[Math.ceil(x)] === 1; +}; \ No newline at end of file diff --git a/src/events/character/zoneLeave.ts b/src/events/character/zoneLeave.ts index 4665269..adb5308 100644 --- a/src/events/character/zoneLeave.ts +++ b/src/events/character/zoneLeave.ts @@ -32,8 +32,6 @@ export default function (socket: TSocket, io: Server) { socket.leave(zone.id.toString()) - socket.emit('character:zone:unload') - // let other clients know of new character io.to(zone.id.toString()).emit('zone:character:leave', socket.character) diff --git a/src/events/character/zoneRequest.ts b/src/events/character/zoneRequest.ts index 790631e..06303e0 100644 --- a/src/events/character/zoneRequest.ts +++ b/src/events/character/zoneRequest.ts @@ -3,6 +3,7 @@ import { TSocket } from '../../utilities/types' import ZoneRepository from '../../repositories/zoneRepository' import ZoneManager from '../../managers/zoneManager' import { Character, Zone } from '@prisma/client' +import logger from '../../utilities/logger' interface IPayload { zoneId: number @@ -20,29 +21,42 @@ interface IResponse { */ export default function (socket: TSocket, io: Server) { socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => { - console.log(`---User ${socket.character?.id} has requested zone.`) + try { + console.log(`---User ${socket.character?.id} has requested zone.`) - if (!data.zoneId) { - console.log(`---Zone id not provided.`) - return + if (!socket.character) return; + + if (!data.zoneId) { + console.log(`---Zone id not provided.`) + return + } + + const zone = await ZoneRepository.getById(data.zoneId) + + if (!zone) { + console.log(`---Zone not found.`) + return + } + + if (socket.character?.zoneId) { + socket.leave(socket.character.zoneId.toString()) + io.to(socket.character.zoneId.toString()).emit('zone:character:leave', socket.character) + } + + socket.character.zoneId = zone.id + socket.join(zone.id.toString()) + + // let other clients know of new character + io.to(zone.id.toString()).emit('zone:character:join', socket.character) + + // add character to zone manager + ZoneManager.addCharacterToZone(zone.id, socket.character as Character) + + // send over zone and characters to socket + callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) }) + } catch (error: any) { + console.log(`Error requesting zone: ${error.message}`) + logger.error(`Error requesting zone: ${error.message}`) } - - const zone = await ZoneRepository.getById(data.zoneId) - - if (!zone) { - console.log(`---Zone not found.`) - return - } - - socket.join(zone.id.toString()) - - // send over zone and characters to socket - callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) }) - - // let other clients know of new character - io.to(zone.id.toString()).emit('zone:character:join', socket.character) - - // add character to zone manager - ZoneManager.addCharacterToZone(zone.id, socket.character as Character) }) } diff --git a/src/events/gm/zone/update.ts b/src/events/gm/zone/update.ts index 48e4c26..de4df14 100644 --- a/src/events/gm/zone/update.ts +++ b/src/events/gm/zone/update.ts @@ -73,17 +73,15 @@ export default function (socket: TSocket, io: Server) { type: zoneEventTile.type, positionX: zoneEventTile.positionX, positionY: zoneEventTile.positionY, - ...(zoneEventTile.type === 'TELEPORT' && zoneEventTile.teleport - ? { - teleport: { - create: { - toZoneId: zoneEventTile.teleport.toZoneId, - toPositionX: zoneEventTile.teleport.toPositionX, - toPositionY: zoneEventTile.teleport.toPositionY - } + ...(zoneEventTile.type === 'TELEPORT' && zoneEventTile.teleport ? { + teleport: { + create: { + toZoneId: zoneEventTile.teleport.toZoneId, + toPositionX: zoneEventTile.teleport.toPositionX, + toPositionY: zoneEventTile.teleport.toPositionY } } - : {}) + } : {}) })) }, zoneObjects: {