forked from noxious/server
New method for events + TP work
This commit is contained in:
parent
ea9639b440
commit
e8d100e063
6
package-lock.json
generated
6
package-lock.json
generated
@ -2129,9 +2129,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/safe-stable-stringify": {
|
"node_modules/safe-stable-stringify": {
|
||||||
"version": "2.4.3",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||||
"integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
|
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
@ -73,15 +73,17 @@ 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: {
|
? {
|
||||||
create: {
|
teleport: {
|
||||||
toZoneId: zoneEventTile.teleport.toZoneId,
|
create: {
|
||||||
toPositionX: zoneEventTile.teleport.toPositionX,
|
toZoneId: zoneEventTile.teleport.toZoneId,
|
||||||
toPositionY: zoneEventTile.teleport.toPositionY
|
toPositionX: zoneEventTile.teleport.toPositionX,
|
||||||
|
toPositionY: zoneEventTile.teleport.toPositionY
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} : {})
|
: {})
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
zoneObjects: {
|
zoneObjects: {
|
||||||
|
@ -24,7 +24,7 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
try {
|
try {
|
||||||
console.log(`---User ${socket.character?.id} has requested zone.`)
|
console.log(`---User ${socket.character?.id} has requested zone.`)
|
||||||
|
|
||||||
if (!socket.character) return;
|
if (!socket.character) return
|
||||||
|
|
||||||
if (!data.zoneId) {
|
if (!data.zoneId) {
|
||||||
console.log(`---Zone id not provided.`)
|
console.log(`---Zone id not provided.`)
|
||||||
@ -55,7 +55,7 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) })
|
callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) })
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(`Error requesting zone: ${error.message}`)
|
logger.error(`Error requesting zone: ${error.message}`)
|
||||||
socket.disconnect();
|
socket.disconnect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,158 +0,0 @@
|
|||||||
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<number, symbol>();
|
|
||||||
|
|
||||||
export default function setupCharacterMove(socket: TSocket, io: Server) {
|
|
||||||
socket.on('character:initMove', handleCharacterMove(socket, io));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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];
|
|
||||||
|
|
||||||
while (Date.now() - startTime < stepDuration) {
|
|
||||||
if (moveTokens.get(character.id) !== moveToken) return;
|
|
||||||
|
|
||||||
const progress = (Date.now() - startTime) / stepDuration;
|
|
||||||
const current = interpolatePosition(start, end, progress);
|
|
||||||
|
|
||||||
if (isObstacle(current, grid)) {
|
|
||||||
await updateCharacterPosition(character, start, Rotation.calculate(start.x, start.y, end.x, end.y), socket, io);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moveTokens.get(character.id) === moveToken) {
|
|
||||||
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());
|
|
||||||
|
|
||||||
socket.emit('zone:teleport', { zone, characters: ZoneManager.getCharactersInZone(zone.id) });
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
70
src/events/zone/characterMoveEvent.ts
Normal file
70
src/events/zone/characterMoveEvent.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { TSocket, ExtendedCharacter } from '../../utilities/types'
|
||||||
|
import { CharacterMoveService } from '../../services/character/characterMoveService'
|
||||||
|
import { TeleportService } from '../../services/character/teleportService'
|
||||||
|
import { MovementValidator } from '../../services/character/movementValidator'
|
||||||
|
import { SocketEmitter } from '../../utilities/socketEmitter'
|
||||||
|
|
||||||
|
export default class CharacterMoveEvent {
|
||||||
|
private characterMoveService: CharacterMoveService
|
||||||
|
private teleportService: TeleportService
|
||||||
|
private movementValidator: MovementValidator
|
||||||
|
private socketEmitter: SocketEmitter
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {
|
||||||
|
this.characterMoveService = new CharacterMoveService()
|
||||||
|
this.teleportService = new TeleportService()
|
||||||
|
this.movementValidator = new MovementValidator()
|
||||||
|
this.socketEmitter = new SocketEmitter(io, socket)
|
||||||
|
}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
this.socket.on('character:initMove', this.handleCharacterMove.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleCharacterMove({ positionX, positionY }: { positionX: number; positionY: number }): Promise<void> {
|
||||||
|
const { character } = this.socket
|
||||||
|
if (!character) {
|
||||||
|
console.error('character:move error', 'Character not found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = await this.characterMoveService.calculatePath(character, positionX, positionY)
|
||||||
|
if (!path) {
|
||||||
|
this.socketEmitter.emitMoveError('No valid path found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.moveAlongPath(character, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
|
||||||
|
for (const position of path) {
|
||||||
|
if (!(await this.movementValidator.isValidMove(character, position))) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const teleport = await this.teleportService.checkForTeleport(character, position)
|
||||||
|
if (teleport) {
|
||||||
|
await this.characterMoveService.updatePosition(character, position, teleport.toZoneId)
|
||||||
|
await this.teleportService.handleTeleport(this.socket, character, teleport)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.characterMoveService.updatePosition(character, position)
|
||||||
|
this.socketEmitter.emitCharacterMove(character)
|
||||||
|
|
||||||
|
await this.characterMoveService.applyMovementDelay()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finalizeMovement(character)
|
||||||
|
}
|
||||||
|
|
||||||
|
private finalizeMovement(character: ExtendedCharacter): void {
|
||||||
|
character.isMoving = false
|
||||||
|
this.socketEmitter.emitCharacterMove(character)
|
||||||
|
}
|
||||||
|
}
|
@ -57,42 +57,42 @@ class ZoneManager {
|
|||||||
private isPositionWalkable(zoneId: number, x: number, y: number): boolean {
|
private isPositionWalkable(zoneId: number, x: number, y: number): boolean {
|
||||||
const loadedZone = this.loadedZones.find((lz) => lz.zone.id === zoneId)
|
const loadedZone = this.loadedZones.find((lz) => lz.zone.id === zoneId)
|
||||||
if (!loadedZone) {
|
if (!loadedZone) {
|
||||||
console.log(`Zone ${zoneId} not found in loadedZones`);
|
console.log(`Zone ${zoneId} not found in loadedZones`)
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
if (!loadedZone.grid) {
|
if (!loadedZone.grid) {
|
||||||
console.log(`Grid for zone ${zoneId} is undefined`);
|
console.log(`Grid for zone ${zoneId} is undefined`)
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
if (!loadedZone.grid[y]) {
|
if (!loadedZone.grid[y]) {
|
||||||
console.log(`Row ${y} in grid for zone ${zoneId} is undefined`);
|
console.log(`Row ${y} in grid for zone ${zoneId} is undefined`)
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
return loadedZone.grid[y][x] === 0;
|
return loadedZone.grid[y][x] === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
public addCharacterToZone(zoneId: number, character: Character) {
|
public addCharacterToZone(zoneId: number, character: Character) {
|
||||||
console.log(`Adding character ${character.id} to zone ${zoneId}`);
|
console.log(`Adding character ${character.id} to zone ${zoneId}`)
|
||||||
console.log(`Character position: x=${character.positionX}, y=${character.positionY}`);
|
console.log(`Character position: x=${character.positionX}, y=${character.positionY}`)
|
||||||
|
|
||||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
const loadedZone = this.loadedZones.find((loadedZone) => {
|
||||||
return loadedZone.zone.id === zoneId
|
return loadedZone.zone.id === zoneId
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!loadedZone) {
|
if (!loadedZone) {
|
||||||
console.log(`Zone ${zoneId} not found in loadedZones`);
|
console.log(`Zone ${zoneId} not found in loadedZones`)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isPositionWalkable(zoneId, character.positionX, character.positionY)) {
|
if (this.isPositionWalkable(zoneId, character.positionX, character.positionY)) {
|
||||||
loadedZone.characters.push(character)
|
loadedZone.characters.push(character)
|
||||||
console.log(`Character ${character.id} added to zone ${zoneId}`);
|
console.log(`Character ${character.id} added to zone ${zoneId}`)
|
||||||
} else {
|
} else {
|
||||||
// set position to 0,0 if not walkable
|
// set position to 0,0 if not walkable
|
||||||
console.log(`Position (${character.positionX}, ${character.positionY}) is not walkable in zone ${zoneId}`);
|
console.log(`Position (${character.positionX}, ${character.positionY}) is not walkable in zone ${zoneId}`)
|
||||||
character.positionX = 0;
|
character.positionX = 0
|
||||||
character.positionY = 0;
|
character.positionY = 0
|
||||||
loadedZone.characters.push(character);
|
loadedZone.characters.push(character)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,18 @@ export class Server {
|
|||||||
* Start the server
|
* Start the server
|
||||||
*/
|
*/
|
||||||
public async start() {
|
public async start() {
|
||||||
|
// Read log file and print to console for debugging
|
||||||
|
const logFile = path.join(__dirname, '../logs/app.log')
|
||||||
|
|
||||||
|
fs.watchFile(logFile, (curr, prev) => {
|
||||||
|
if (curr.size > prev.size) {
|
||||||
|
const stream = fs.createReadStream(logFile, { start: prev.size, end: curr.size })
|
||||||
|
stream.on('data', (chunk) => {
|
||||||
|
console.log(chunk.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Check prisma connection
|
// Check prisma connection
|
||||||
try {
|
try {
|
||||||
await prisma.$connect()
|
await prisma.$connect()
|
||||||
@ -91,9 +103,25 @@ export class Server {
|
|||||||
|
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
await this.loadEventHandlers(fullPath, socket)
|
await this.loadEventHandlers(fullPath, socket)
|
||||||
} else if (file.isFile()) {
|
} else if (file.isFile() && file.name.endsWith('.ts')) {
|
||||||
const module = await import(fullPath)
|
try {
|
||||||
module.default(socket, this.io)
|
const module = await import(fullPath)
|
||||||
|
if (typeof module.default === 'function') {
|
||||||
|
if (module.default.prototype && module.default.prototype.listen) {
|
||||||
|
// This is a class-based event
|
||||||
|
const EventClass = module.default
|
||||||
|
const eventInstance = new EventClass(this.io, socket)
|
||||||
|
eventInstance.listen()
|
||||||
|
} else {
|
||||||
|
// This is a function-based event
|
||||||
|
module.default(socket, this.io)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn(`Unrecognized export in ${file.name}`)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
logger.error(`Error loading event handler ${file.name}: ${error.message}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
66
src/services/character/characterMoveService.ts
Normal file
66
src/services/character/characterMoveService.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { ExtendedCharacter } from '../../utilities/types'
|
||||||
|
import { AStar } from '../../utilities/character/aStar'
|
||||||
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
|
import prisma from '../../utilities/prisma'
|
||||||
|
import Rotation from '../../utilities/character/rotation'
|
||||||
|
|
||||||
|
export class CharacterMoveService {
|
||||||
|
private moveTokens: Map<number, symbol> = new Map()
|
||||||
|
|
||||||
|
public async updatePosition(character: ExtendedCharacter, position: { x: number; y: number }, newZoneId?: number): Promise<void> {
|
||||||
|
const oldZoneId = character.zoneId
|
||||||
|
|
||||||
|
Object.assign(character, {
|
||||||
|
positionX: position.x,
|
||||||
|
positionY: position.y,
|
||||||
|
rotation: Rotation.calculate(character.positionX, character.positionY, position.x, position.y),
|
||||||
|
zoneId: newZoneId || character.zoneId
|
||||||
|
})
|
||||||
|
|
||||||
|
if (newZoneId && newZoneId !== oldZoneId) {
|
||||||
|
ZoneManager.removeCharacterFromZone(oldZoneId, character)
|
||||||
|
ZoneManager.addCharacterToZone(newZoneId, character)
|
||||||
|
} else {
|
||||||
|
ZoneManager.updateCharacterInZone(character.zoneId, character)
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.character.update({
|
||||||
|
where: { id: character.id },
|
||||||
|
data: {
|
||||||
|
positionX: position.x,
|
||||||
|
positionY: position.y,
|
||||||
|
rotation: character.rotation,
|
||||||
|
zoneId: character.zoneId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async calculatePath(character: ExtendedCharacter, targetX: number, targetY: number): Promise<Array<{ x: number; y: number }> | null> {
|
||||||
|
const grid = await ZoneManager.getGrid(character.zoneId)
|
||||||
|
if (!grid?.length) {
|
||||||
|
console.error('character:move error', 'Grid not found or empty')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = { x: Math.floor(character.positionX), y: Math.floor(character.positionY) }
|
||||||
|
const end = { x: Math.floor(targetX), y: Math.floor(targetY) }
|
||||||
|
|
||||||
|
return AStar.findPath(start, end, grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
public startMovement(characterId: number): void {
|
||||||
|
this.moveTokens.set(characterId, Symbol('moveToken'))
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopMovement(characterId: number): void {
|
||||||
|
this.moveTokens.delete(characterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public isMoving(characterId: number): boolean {
|
||||||
|
return this.moveTokens.has(characterId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async applyMovementDelay(): Promise<void> {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 250)) // 50ms delay between steps
|
||||||
|
}
|
||||||
|
}
|
17
src/services/character/movementValidator.ts
Normal file
17
src/services/character/movementValidator.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ExtendedCharacter } from '../../utilities/types'
|
||||||
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
|
|
||||||
|
export class MovementValidator {
|
||||||
|
public async isValidMove(character: ExtendedCharacter, position: { x: number; y: number }): Promise<boolean> {
|
||||||
|
const grid = await ZoneManager.getGrid(character.zoneId)
|
||||||
|
if (!grid?.length) return false
|
||||||
|
|
||||||
|
return !this.isObstacle(position, grid)
|
||||||
|
}
|
||||||
|
|
||||||
|
private isObstacle({ x, y }: { x: number; y: number }, grid: number[][]): boolean {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
37
src/services/character/teleportService.ts
Normal file
37
src/services/character/teleportService.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { ExtendedCharacter, TSocket } from '../../utilities/types'
|
||||||
|
import prisma from '../../utilities/prisma'
|
||||||
|
import ZoneRepository from '../../repositories/zoneRepository'
|
||||||
|
import ZoneManager from '../../managers/zoneManager'
|
||||||
|
|
||||||
|
export class TeleportService {
|
||||||
|
public async checkForTeleport(character: ExtendedCharacter, position: { x: number; y: number }): Promise<any | null> {
|
||||||
|
return prisma.zoneEventTile.findFirst({
|
||||||
|
where: {
|
||||||
|
zoneId: character.zoneId,
|
||||||
|
type: 'TELEPORT',
|
||||||
|
positionX: Math.floor(position.x),
|
||||||
|
positionY: Math.floor(position.y)
|
||||||
|
},
|
||||||
|
include: { teleport: true }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleTeleport(socket: TSocket, character: ExtendedCharacter, teleport: any): Promise<void> {
|
||||||
|
if (teleport.toZoneId === character.zoneId) return
|
||||||
|
|
||||||
|
const zone = await ZoneRepository.getById(teleport.toZoneId)
|
||||||
|
if (!zone) return
|
||||||
|
|
||||||
|
character.zoneId = teleport.toZoneId
|
||||||
|
character.positionX = teleport.toPositionX
|
||||||
|
character.positionY = teleport.toPositionY
|
||||||
|
|
||||||
|
socket.leave(character.zoneId.toString())
|
||||||
|
socket.join(teleport.toZoneId.toString())
|
||||||
|
|
||||||
|
socket.emit('zone:teleport', {
|
||||||
|
zone,
|
||||||
|
characters: ZoneManager.getCharactersInZone(zone.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,8 @@ const logger = pino({
|
|||||||
return { level: label.toUpperCase() }
|
return { level: label.toUpperCase() }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
timestamp: pino.stdTimeFunctions.isoTime
|
timestamp: pino.stdTimeFunctions.isoTime,
|
||||||
|
base: null // This will prevent hostname and pid from being included
|
||||||
})
|
})
|
||||||
|
|
||||||
export default logger
|
export default logger
|
||||||
|
29
src/utilities/socketEmitter.ts
Normal file
29
src/utilities/socketEmitter.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Server } from 'socket.io'
|
||||||
|
import { TSocket, ExtendedCharacter } from './types'
|
||||||
|
|
||||||
|
export class SocketEmitter {
|
||||||
|
constructor(
|
||||||
|
private readonly io: Server,
|
||||||
|
private readonly socket: TSocket
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public emitMoveError(message: string): void {
|
||||||
|
this.socket.emit('character:moveError', message)
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitCharacterMove(character: ExtendedCharacter): void {
|
||||||
|
this.io.in(character.zoneId.toString()).emit('character:move', character)
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitCharacterLeave(character: ExtendedCharacter, zoneId: number): void {
|
||||||
|
this.io.to(zoneId.toString()).emit('zone:character:leave', character)
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitCharacterJoin(character: ExtendedCharacter): void {
|
||||||
|
this.io.to(character.zoneId.toString()).emit('zone:character:join', character)
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitCharacterDataUpdated(character: ExtendedCharacter): void {
|
||||||
|
this.socket.emit('character:dataUpdated', character)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user