forked from noxious/server
Added models to store extra data in RAM
This commit is contained in:
parent
32b390bb20
commit
636aa6cc55
6
package-lock.json
generated
6
package-lock.json
generated
@ -706,9 +706,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
|
||||
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
|
||||
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
|
@ -16,7 +16,7 @@ export default class CharacterMoveEvent {
|
||||
private characterMoveService: CharacterMoveService
|
||||
private zoneEventTileService: ZoneEventTileService
|
||||
private movementValidator: MovementValidator
|
||||
private nextPath: {[index: number]: {x: number, y: number}[]} = [];
|
||||
private nextPath: { [index: number]: { x: number; y: number }[] } = []
|
||||
|
||||
constructor(
|
||||
private readonly io: Server,
|
||||
@ -38,7 +38,6 @@ export default class CharacterMoveEvent {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const path = await this.characterMoveService.calculatePath(character, positionX, positionY)
|
||||
if (!path) {
|
||||
this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found')
|
||||
@ -46,8 +45,8 @@ export default class CharacterMoveEvent {
|
||||
}
|
||||
|
||||
if (character.isMoving && !character.resetMovement) {
|
||||
character.resetMovement = true;
|
||||
this.nextPath[character.id] = path;
|
||||
character.resetMovement = true
|
||||
this.nextPath[character.id] = path
|
||||
} else {
|
||||
await this.moveAlongPath(character, path)
|
||||
}
|
||||
@ -55,26 +54,26 @@ export default class CharacterMoveEvent {
|
||||
|
||||
private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const start = path[i];
|
||||
const end = path[i + 1];
|
||||
const start = path[i]
|
||||
const end = path[i + 1]
|
||||
|
||||
if (!(await this.movementValidator.isValidMove(character, end))) {
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
if (character.isMoving && character.resetMovement) {
|
||||
character.isMoving = false;
|
||||
character.resetMovement = false;
|
||||
const nextPath = this.nextPath[character.id];
|
||||
this.moveAlongPath(character, nextPath);
|
||||
break;
|
||||
character.isMoving = false
|
||||
character.resetMovement = false
|
||||
const nextPath = this.nextPath[character.id]
|
||||
this.moveAlongPath(character, nextPath)
|
||||
break
|
||||
}
|
||||
|
||||
if (!character.isMoving) {
|
||||
character.isMoving = true;
|
||||
character.isMoving = true
|
||||
}
|
||||
|
||||
character.rotation = Rotation.calculate(start.x, start.y, end.x, end.y);
|
||||
character.rotation = Rotation.calculate(start.x, start.y, end.x, end.y)
|
||||
|
||||
const zoneEventTile = await prisma.zoneEventTile.findFirst({
|
||||
where: {
|
||||
@ -82,33 +81,33 @@ export default class CharacterMoveEvent {
|
||||
positionX: Math.floor(end.x),
|
||||
positionY: Math.floor(end.y)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
if (zoneEventTile) {
|
||||
if (zoneEventTile.type === 'BLOCK') {
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
if (zoneEventTile.type === 'TELEPORT') {
|
||||
const teleportTile = await prisma.zoneEventTile.findFirst({
|
||||
const teleportTile = (await prisma.zoneEventTile.findFirst({
|
||||
where: { id: zoneEventTile.id },
|
||||
include: { teleport: true }
|
||||
}) as ZoneEventTileWithTeleport;
|
||||
})) as ZoneEventTileWithTeleport
|
||||
|
||||
if (teleportTile) {
|
||||
await this.handleZoneEventTile(teleportTile);
|
||||
break;
|
||||
await this.handleZoneEventTile(teleportTile)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.characterMoveService.updatePosition(character, end);
|
||||
this.io.in(character.zoneId.toString()).emit('character:move', character);
|
||||
await this.characterMoveService.updatePosition(character, end)
|
||||
this.io.in(character.zoneId.toString()).emit('character:move', character)
|
||||
|
||||
await this.characterMoveService.applyMovementDelay();
|
||||
await this.characterMoveService.applyMovementDelay()
|
||||
}
|
||||
|
||||
this.finalizeMovement(character);
|
||||
this.finalizeMovement(character)
|
||||
}
|
||||
|
||||
private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise<void> {
|
||||
|
@ -3,15 +3,10 @@ import ZoneRepository from '../repositories/zoneRepository'
|
||||
import ZoneService from '../services/zoneService'
|
||||
import zoneRepository from '../repositories/zoneRepository'
|
||||
import logger from '../utilities/logger'
|
||||
|
||||
type TLoadedZone = {
|
||||
zone: Zone
|
||||
characters: Character[]
|
||||
grid: number[][]
|
||||
}
|
||||
import LoadedZone from '../models/zone/loadedZone'
|
||||
|
||||
class ZoneManager {
|
||||
private loadedZones: TLoadedZone[] = []
|
||||
private loadedZones: LoadedZone[] = []
|
||||
|
||||
// Method to initialize zoneEditor manager
|
||||
public async boot() {
|
||||
@ -31,149 +26,32 @@ class ZoneManager {
|
||||
|
||||
// Method to handle individual zoneEditor loading
|
||||
public async loadZone(zone: Zone) {
|
||||
const grid = await this.getGrid(zone.id) // Create the grid for the zoneEditor
|
||||
this.loadedZones.push({
|
||||
zone,
|
||||
characters: [],
|
||||
grid
|
||||
})
|
||||
const loadedZone = new LoadedZone(zone)
|
||||
this.loadedZones.push(loadedZone)
|
||||
logger.info(`Zone ID ${zone.id} loaded`)
|
||||
}
|
||||
|
||||
// Method to handle individual zoneEditor unloading
|
||||
public unloadZone(zoneId: number) {
|
||||
this.loadedZones = this.loadedZones.filter((loadedZone) => {
|
||||
return loadedZone.zone.id !== zoneId
|
||||
})
|
||||
this.loadedZones = this.loadedZones.filter((loadedZone) => loadedZone.getZone().id !== zoneId)
|
||||
logger.info(`Zone ID ${zoneId} unloaded`)
|
||||
}
|
||||
|
||||
// Getter for loaded zones
|
||||
public getLoadedZones(): TLoadedZone[] {
|
||||
public getLoadedZones(): LoadedZone[] {
|
||||
return this.loadedZones
|
||||
}
|
||||
|
||||
// Check if position is walkable
|
||||
private isPositionWalkable(zoneId: number, x: number, y: number): boolean {
|
||||
const loadedZone = this.loadedZones.find((lz) => lz.zone.id === zoneId)
|
||||
if (!loadedZone) {
|
||||
console.log(`Zone ${zoneId} not found in loadedZones`)
|
||||
return false
|
||||
}
|
||||
if (!loadedZone.grid) {
|
||||
console.log(`Grid for zone ${zoneId} is undefined`)
|
||||
return false
|
||||
}
|
||||
if (!loadedZone.grid[y]) {
|
||||
console.log(`Row ${y} in grid for zone ${zoneId} is undefined`)
|
||||
return false
|
||||
}
|
||||
return loadedZone.grid[y][x] === 0
|
||||
}
|
||||
|
||||
public addCharacterToZone(zoneId: number, character: Character) {
|
||||
console.log(`Adding character ${character.id} to zone ${zoneId}`)
|
||||
console.log(`Character position: x=${character.positionX}, y=${character.positionY}`)
|
||||
|
||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
||||
return loadedZone.zone.id === zoneId
|
||||
})
|
||||
|
||||
if (!loadedZone) {
|
||||
console.log(`Zone ${zoneId} not found in loadedZones`)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isPositionWalkable(zoneId, character.positionX, character.positionY)) {
|
||||
loadedZone.characters.push(character)
|
||||
console.log(`Character ${character.id} added to zone ${zoneId}`)
|
||||
} else {
|
||||
// set position to 0,0 if not walkable
|
||||
console.log(`Position (${character.positionX}, ${character.positionY}) is not walkable in zone ${zoneId}`)
|
||||
character.positionX = 0
|
||||
character.positionY = 0
|
||||
loadedZone.characters.push(character)
|
||||
}
|
||||
}
|
||||
|
||||
public removeCharacterFromZone(zoneId: number, character: Character) {
|
||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
||||
return loadedZone.zone.id === zoneId
|
||||
})
|
||||
if (loadedZone) {
|
||||
loadedZone.characters = loadedZone.characters.filter((loadedCharacter) => {
|
||||
return loadedCharacter.id !== character.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public updateCharacterInZone(zoneId: number, character: Character) {
|
||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
||||
return loadedZone.zone.id === zoneId
|
||||
})
|
||||
if (loadedZone) {
|
||||
const characterIndex = loadedZone.characters.findIndex((loadedCharacter) => {
|
||||
return loadedCharacter.id === character.id
|
||||
})
|
||||
if (characterIndex !== -1) {
|
||||
loadedZone.characters[characterIndex] = character
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getCharactersInZone(zoneId: number): Character[] {
|
||||
const loadedZone = this.loadedZones.find((loadedZone) => {
|
||||
return loadedZone.zone.id === zoneId
|
||||
})
|
||||
return loadedZone ? loadedZone.characters : []
|
||||
}
|
||||
|
||||
public async getGrid(zoneId: number): Promise<number[][]> {
|
||||
const zone = this.loadedZones.find((z) => z.zone.id === zoneId)
|
||||
if (zone) return zone.grid
|
||||
|
||||
const loadedZone = await ZoneRepository.getById(zoneId)
|
||||
if (!loadedZone) return []
|
||||
|
||||
let grid: number[][] = Array.from({ length: loadedZone.height }, () => Array.from({ length: loadedZone.width }, () => 0))
|
||||
|
||||
const eventTiles = await zoneRepository.getEventTiles(zoneId)
|
||||
|
||||
// Set the grid values based on the event tiles, these are strings
|
||||
eventTiles.forEach((eventTile) => {
|
||||
if (eventTile.type === 'BLOCK') {
|
||||
grid[eventTile.positionY][eventTile.positionX] = 1
|
||||
}
|
||||
})
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
public async moveCharacterBetweenZones(oldZoneId: number, newZoneId: number, character: Character): Promise<void> {
|
||||
// Find the old and new zones
|
||||
const oldZone = this.loadedZones.find(zone => zone.zone.id === oldZoneId);
|
||||
const newZone = this.loadedZones.find(zone => zone.zone.id === newZoneId);
|
||||
|
||||
if (!oldZone || !newZone) {
|
||||
logger.error(`Unable to move character ${character.id}: zones not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove character from old zone
|
||||
oldZone.characters = oldZone.characters.filter(c => c.id !== character.id);
|
||||
|
||||
// Check if the new position is walkable
|
||||
if (this.isPositionWalkable(newZoneId, character.positionX, character.positionY)) {
|
||||
newZone.characters.push(character);
|
||||
} else {
|
||||
// Set position to 0,0 if not walkable
|
||||
logger.warn(`Position (${character.positionX}, ${character.positionY}) is not walkable in zone ${newZoneId}. Moving character to (0,0).`);
|
||||
character.positionX = 0;
|
||||
character.positionY = 0;
|
||||
newZone.characters.push(character);
|
||||
}
|
||||
|
||||
logger.info(`Character ${character.id} moved from zone ${oldZoneId} to zone ${newZoneId}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
60
src/models/zone/loadedZone.ts
Normal file
60
src/models/zone/loadedZone.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Character, Tile, Zone } from '@prisma/client'
|
||||
import ZoneCharacter from './zoneCharacter'
|
||||
import zoneRepository from '../../repositories/zoneRepository'
|
||||
import { ExtendedCharacter } from '../../utilities/types'
|
||||
import ZoneManager from '../../managers/zoneManager'
|
||||
|
||||
class LoadedZone {
|
||||
private readonly zone: Zone
|
||||
private characters: ZoneCharacter[] = []
|
||||
// private readonly npcs: ZoneNPC[] = []
|
||||
private readonly grid: number[][] = []
|
||||
|
||||
constructor(zone: Zone) {
|
||||
this.zone = zone
|
||||
}
|
||||
|
||||
public getZone(): Zone {
|
||||
return this.zone
|
||||
}
|
||||
|
||||
public getCharacters(): ZoneCharacter[] {
|
||||
return this.characters
|
||||
}
|
||||
|
||||
public addCharacter(character: Character): void {
|
||||
const zoneCharacter = new ZoneCharacter(character)
|
||||
this.characters.push(zoneCharacter)
|
||||
}
|
||||
|
||||
public removeCharacter(character: Character): void {
|
||||
this.characters = this.characters.filter((zoneCharacter) => zoneCharacter.getCharacter().id !== character.id)
|
||||
}
|
||||
|
||||
public async getGrid(): Promise<number[][]> {
|
||||
let grid: number[][] = Array.from({ length: this.zone..height }, () => Array.from({ length: this.zone.width }, () => 0))
|
||||
|
||||
const eventTiles = await zoneRepository.getEventTiles(this.zone.id)
|
||||
|
||||
// Set the grid values based on the event tiles, these are strings
|
||||
eventTiles.forEach((eventTile) => {
|
||||
if (eventTile.type === 'BLOCK') {
|
||||
grid[eventTile.positionY][eventTile.positionX] = 1
|
||||
}
|
||||
})
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
public async isPositionWalkable(position: { x: number; y: number }): Promise<boolean> {
|
||||
const grid = await this.getGrid()
|
||||
if (!grid?.length) return false
|
||||
|
||||
const gridX = Math.floor(position.x)
|
||||
const gridY = Math.floor(position.y)
|
||||
|
||||
return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(position.x)] === 1 || grid[Math.ceil(position.y)]?.[gridX] === 1 || grid[Math.ceil(position.y)]?.[Math.ceil(position.x)] === 1
|
||||
}
|
||||
}
|
||||
|
||||
export default LoadedZone
|
@ -1,6 +1,6 @@
|
||||
import { Character } from '@prisma/client'
|
||||
|
||||
class ZoneCharacter {
|
||||
export default class ZoneCharacter {
|
||||
private readonly character: Character
|
||||
private isMoving: boolean = false
|
||||
|
@ -7,13 +7,13 @@ import { Server } from 'socket.io'
|
||||
|
||||
export class ZoneEventTileService {
|
||||
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> {
|
||||
if (teleport.toZoneId === character.zoneId) return;
|
||||
if (teleport.toZoneId === character.zoneId) return
|
||||
|
||||
const zone = await ZoneRepository.getById(teleport.toZoneId);
|
||||
if (!zone) return;
|
||||
const zone = await ZoneRepository.getById(teleport.toZoneId)
|
||||
if (!zone) return
|
||||
|
||||
const oldZoneId = character.zoneId;
|
||||
const newZoneId = teleport.toZoneId;
|
||||
const oldZoneId = character.zoneId
|
||||
const newZoneId = teleport.toZoneId
|
||||
|
||||
// Update character in database
|
||||
await prisma.character.update({
|
||||
@ -23,28 +23,28 @@ export class ZoneEventTileService {
|
||||
positionX: teleport.toPositionX,
|
||||
positionY: teleport.toPositionY
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// Update local character object
|
||||
character.zoneId = newZoneId;
|
||||
character.positionX = teleport.toPositionX;
|
||||
character.positionY = teleport.toPositionY;
|
||||
character.zoneId = newZoneId
|
||||
character.positionX = teleport.toPositionX
|
||||
character.positionY = teleport.toPositionY
|
||||
|
||||
// Atomic operation in ZoneManager
|
||||
await ZoneManager.moveCharacterBetweenZones(oldZoneId, newZoneId, character as Character);
|
||||
await ZoneManager.moveCharacterBetweenZones(oldZoneId, newZoneId, character as Character)
|
||||
|
||||
// Emit events
|
||||
io.to(oldZoneId.toString()).emit('zone:character:leave', character.id);
|
||||
io.to(newZoneId.toString()).emit('zone:character:join', character);
|
||||
io.to(oldZoneId.toString()).emit('zone:character:leave', character.id)
|
||||
io.to(newZoneId.toString()).emit('zone:character:join', character)
|
||||
|
||||
// Update socket rooms
|
||||
socket.leave(oldZoneId.toString());
|
||||
socket.join(newZoneId.toString());
|
||||
socket.leave(oldZoneId.toString())
|
||||
socket.join(newZoneId.toString())
|
||||
|
||||
// Send teleport information to the client
|
||||
socket.emit('zone:teleport', {
|
||||
zone,
|
||||
characters: ZoneManager.getCharactersInZone(zone.id)
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,5 @@ import { ExtendedCharacter } from '../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
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user