Work for teleports
This commit is contained in:
parent
c909bc4aa7
commit
e0b376cb83
@ -1,93 +1,156 @@
|
|||||||
import { Server } from 'socket.io'
|
import { Server } from 'socket.io';
|
||||||
import { TSocket, ExtendedCharacter } from '../../utilities/types'
|
import { TSocket, ExtendedCharacter } from '../../utilities/types';
|
||||||
import ZoneManager from '../../managers/zoneManager'
|
import ZoneManager from '../../managers/zoneManager';
|
||||||
import prisma from '../../utilities/prisma'
|
import prisma from '../../utilities/prisma';
|
||||||
import { AStar } from '../../utilities/player/aStar'
|
import { AStar } from '../../utilities/player/aStar';
|
||||||
import Rotation from '../../utilities/player/rotation'
|
import Rotation from '../../utilities/player/rotation';
|
||||||
|
import ZoneRepository from '../../repositories/zoneRepository';
|
||||||
|
import { Character } from '@prisma/client';
|
||||||
|
|
||||||
const moveTokens = new Map<number, symbol>()
|
const moveTokens = new Map<number, symbol>();
|
||||||
|
|
||||||
export default function setupCharacterMove(socket: TSocket, io: Server) {
|
export default function setupCharacterMove(socket: TSocket, io: Server) {
|
||||||
socket.on('character:move', async ({ positionX, positionY }: { positionX: number; positionY: number }) => {
|
socket.on('character:initMove', handleCharacterMove(socket, io));
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moveAlongPath(socket: TSocket, io: Server, path: Array<{ x: number; y: number }>, grid: number[][]) {
|
const handleCharacterMove = (socket: TSocket, io: Server) => async ({ positionX, positionY }: { positionX: number; positionY: number }) => {
|
||||||
const { character } = socket
|
const { character } = socket;
|
||||||
if (!character) return
|
if (!character) return console.error('character:move error', 'Character not found');
|
||||||
|
|
||||||
const moveToken = moveTokens.get(character.id)
|
const grid = await ZoneManager.getGrid(character.zoneId);
|
||||||
const stepDuration = 250
|
if (!grid?.length) return console.error('character:move error', 'Grid not found or empty');
|
||||||
const updateInterval = 50
|
|
||||||
|
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++) {
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
const startTime = Date.now()
|
const startTime = Date.now();
|
||||||
const start = path[i]
|
const start = path[i];
|
||||||
const end = path[i + 1]
|
const end = path[i + 1];
|
||||||
|
|
||||||
while (Date.now() - startTime < stepDuration) {
|
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 progress = (Date.now() - startTime) / stepDuration;
|
||||||
const current = interpolatePosition(start, end, progress)
|
const current = interpolatePosition(start, end, progress);
|
||||||
|
|
||||||
if (isObstacle(current, grid)) {
|
if (isObstacle(current, grid)) {
|
||||||
// If obstacle encountered, stop at the last valid position
|
await updateCharacterPosition(character, start, Rotation.calculate(start.x, start.y, end.x, end.y), socket, io);
|
||||||
await updateCharacter(character, start, Rotation.calculate(start.x, start.y, end.x, end.y))
|
return;
|
||||||
io.in(character.zoneId.toString()).emit('character:moved', character)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateCharacter(character, current, Rotation.calculate(start.x, start.y, end.x, end.y))
|
const tp = await prisma.zoneEventTile.findFirst({
|
||||||
io.in(character.zoneId.toString()).emit('character:moved', character)
|
where: { zoneId: character.zoneId, type: 'TELEPORT', positionX: current.x, positionY: current.y },
|
||||||
await new Promise((resolve) => setTimeout(resolve, updateInterval))
|
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) {
|
if (moveTokens.get(character.id) === moveToken) {
|
||||||
const finalPosition = path[path.length - 1]
|
await updateCharacterPosition(character, path[path.length - 1], character.rotation, socket, io);
|
||||||
await updateCharacter(character, finalPosition, character.rotation)
|
character.isMoving = false;
|
||||||
character.isMoving = false
|
moveTokens.delete(character.id);
|
||||||
moveTokens.delete(character.id)
|
|
||||||
io.in(character.zoneId.toString()).emit('character:moved', character)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) => ({
|
const interpolatePosition = (start: { x: number; y: number }, end: { x: number; y: number }, progress: number) => ({
|
||||||
x: start.x + (end.x - start.x) * progress,
|
x: start.x + (end.x - start.x) * progress,
|
||||||
y: start.y + (end.y - start.y) * progress
|
y: start.y + (end.y - start.y) * progress
|
||||||
})
|
});
|
||||||
|
|
||||||
const isObstacle = ({ x, y }: { x: number; y: number }, grid: number[][]) => {
|
const isObstacle = ({ x, y }: { x: number; y: number }, grid: number[][]) => {
|
||||||
const gridX = Math.floor(x)
|
const gridX = Math.floor(x);
|
||||||
const gridY = Math.floor(y)
|
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
|
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 }
|
|
||||||
})
|
|
||||||
}
|
|
@ -32,8 +32,6 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
|
|
||||||
socket.leave(zone.id.toString())
|
socket.leave(zone.id.toString())
|
||||||
|
|
||||||
socket.emit('character:zone:unload')
|
|
||||||
|
|
||||||
// let other clients know of new character
|
// let other clients know of new character
|
||||||
io.to(zone.id.toString()).emit('zone:character:leave', socket.character)
|
io.to(zone.id.toString()).emit('zone:character:leave', socket.character)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { TSocket } from '../../utilities/types'
|
|||||||
import ZoneRepository from '../../repositories/zoneRepository'
|
import ZoneRepository from '../../repositories/zoneRepository'
|
||||||
import ZoneManager from '../../managers/zoneManager'
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
import { Character, Zone } from '@prisma/client'
|
import { Character, Zone } from '@prisma/client'
|
||||||
|
import logger from '../../utilities/logger'
|
||||||
|
|
||||||
interface IPayload {
|
interface IPayload {
|
||||||
zoneId: number
|
zoneId: number
|
||||||
@ -20,29 +21,42 @@ interface IResponse {
|
|||||||
*/
|
*/
|
||||||
export default function (socket: TSocket, io: Server) {
|
export default function (socket: TSocket, io: Server) {
|
||||||
socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => {
|
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) {
|
if (!socket.character) return;
|
||||||
console.log(`---Zone id not provided.`)
|
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -73,17 +73,15 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
type: zoneEventTile.type,
|
type: zoneEventTile.type,
|
||||||
positionX: zoneEventTile.positionX,
|
positionX: zoneEventTile.positionX,
|
||||||
positionY: zoneEventTile.positionY,
|
positionY: zoneEventTile.positionY,
|
||||||
...(zoneEventTile.type === 'TELEPORT' && zoneEventTile.teleport
|
...(zoneEventTile.type === 'TELEPORT' && zoneEventTile.teleport ? {
|
||||||
? {
|
teleport: {
|
||||||
teleport: {
|
create: {
|
||||||
create: {
|
toZoneId: zoneEventTile.teleport.toZoneId,
|
||||||
toZoneId: zoneEventTile.teleport.toZoneId,
|
toPositionX: zoneEventTile.teleport.toPositionX,
|
||||||
toPositionX: zoneEventTile.teleport.toPositionX,
|
toPositionY: zoneEventTile.teleport.toPositionY
|
||||||
toPositionY: zoneEventTile.teleport.toPositionY
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {})
|
} : {})
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
zoneObjects: {
|
zoneObjects: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user