1
0
forked from noxious/server

Added models to store extra data in RAM

This commit is contained in:
Dennis Postma 2024-09-09 18:31:12 +02:00
parent 32b390bb20
commit 636aa6cc55
11 changed files with 116 additions and 190 deletions

6
package-lock.json generated
View File

@ -706,9 +706,9 @@
} }
}, },
"node_modules/acorn-walk": { "node_modules/acorn-walk": {
"version": "8.3.3", "version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"acorn": "^8.11.0" "acorn": "^8.11.0"

View File

@ -16,7 +16,7 @@ export default class CharacterMoveEvent {
private characterMoveService: CharacterMoveService private characterMoveService: CharacterMoveService
private zoneEventTileService: ZoneEventTileService private zoneEventTileService: ZoneEventTileService
private movementValidator: MovementValidator private movementValidator: MovementValidator
private nextPath: {[index: number]: {x: number, y: number}[]} = []; private nextPath: { [index: number]: { x: number; y: number }[] } = []
constructor( constructor(
private readonly io: Server, private readonly io: Server,
@ -38,16 +38,15 @@ export default class CharacterMoveEvent {
return return
} }
const path = await this.characterMoveService.calculatePath(character, positionX, positionY) const path = await this.characterMoveService.calculatePath(character, positionX, positionY)
if (!path) { if (!path) {
this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found') this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found')
return return
} }
if(character.isMoving && !character.resetMovement) { if (character.isMoving && !character.resetMovement) {
character.resetMovement = true; character.resetMovement = true
this.nextPath[character.id] = path; this.nextPath[character.id] = path
} else { } else {
await this.moveAlongPath(character, path) 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> { private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
for (let i = 0; i < path.length - 1; i++) { for (let i = 0; i < path.length - 1; i++) {
const start = path[i]; const start = path[i]
const end = path[i + 1]; const end = path[i + 1]
if (!(await this.movementValidator.isValidMove(character, end))) { if (!(await this.movementValidator.isValidMove(character, end))) {
break; break
} }
if(character.isMoving && character.resetMovement) { if (character.isMoving && character.resetMovement) {
character.isMoving = false; character.isMoving = false
character.resetMovement = false; character.resetMovement = false
const nextPath = this.nextPath[character.id]; const nextPath = this.nextPath[character.id]
this.moveAlongPath(character, nextPath); this.moveAlongPath(character, nextPath)
break; break
} }
if(!character.isMoving) { 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({ const zoneEventTile = await prisma.zoneEventTile.findFirst({
where: { where: {
@ -82,33 +81,33 @@ export default class CharacterMoveEvent {
positionX: Math.floor(end.x), positionX: Math.floor(end.x),
positionY: Math.floor(end.y) positionY: Math.floor(end.y)
} }
}); })
if (zoneEventTile) { if (zoneEventTile) {
if (zoneEventTile.type === 'BLOCK') { if (zoneEventTile.type === 'BLOCK') {
break; break
} }
if (zoneEventTile.type === 'TELEPORT') { if (zoneEventTile.type === 'TELEPORT') {
const teleportTile = await prisma.zoneEventTile.findFirst({ const teleportTile = (await prisma.zoneEventTile.findFirst({
where: { id: zoneEventTile.id }, where: { id: zoneEventTile.id },
include: { teleport: true } include: { teleport: true }
}) as ZoneEventTileWithTeleport; })) as ZoneEventTileWithTeleport
if (teleportTile) { if (teleportTile) {
await this.handleZoneEventTile(teleportTile); await this.handleZoneEventTile(teleportTile)
break; break
} }
} }
} }
await this.characterMoveService.updatePosition(character, end); await this.characterMoveService.updatePosition(character, end)
this.io.in(character.zoneId.toString()).emit('character:move', character); 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> { private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise<void> {

View File

@ -3,15 +3,10 @@ import ZoneRepository from '../repositories/zoneRepository'
import ZoneService from '../services/zoneService' import ZoneService from '../services/zoneService'
import zoneRepository from '../repositories/zoneRepository' import zoneRepository from '../repositories/zoneRepository'
import logger from '../utilities/logger' import logger from '../utilities/logger'
import LoadedZone from '../models/zone/loadedZone'
type TLoadedZone = {
zone: Zone
characters: Character[]
grid: number[][]
}
class ZoneManager { class ZoneManager {
private loadedZones: TLoadedZone[] = [] private loadedZones: LoadedZone[] = []
// Method to initialize zoneEditor manager // Method to initialize zoneEditor manager
public async boot() { public async boot() {
@ -31,149 +26,32 @@ class ZoneManager {
// Method to handle individual zoneEditor loading // Method to handle individual zoneEditor loading
public async loadZone(zone: Zone) { public async loadZone(zone: Zone) {
const grid = await this.getGrid(zone.id) // Create the grid for the zoneEditor const loadedZone = new LoadedZone(zone)
this.loadedZones.push({ this.loadedZones.push(loadedZone)
zone,
characters: [],
grid
})
logger.info(`Zone ID ${zone.id} loaded`) logger.info(`Zone ID ${zone.id} loaded`)
} }
// Method to handle individual zoneEditor unloading // Method to handle individual zoneEditor unloading
public unloadZone(zoneId: number) { public unloadZone(zoneId: number) {
this.loadedZones = this.loadedZones.filter((loadedZone) => { this.loadedZones = this.loadedZones.filter((loadedZone) => loadedZone.getZone().id !== zoneId)
return loadedZone.zone.id !== zoneId
})
logger.info(`Zone ID ${zoneId} unloaded`) logger.info(`Zone ID ${zoneId} unloaded`)
} }
// Getter for loaded zones // Getter for loaded zones
public getLoadedZones(): TLoadedZone[] { public getLoadedZones(): LoadedZone[] {
return this.loadedZones 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) { 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) { 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> { 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}`);
} }
} }

View 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

View File

@ -1,6 +1,6 @@
import { Character } from '@prisma/client' import { Character } from '@prisma/client'
class ZoneCharacter { export default class ZoneCharacter {
private readonly character: Character private readonly character: Character
private isMoving: boolean = false private isMoving: boolean = false

View File

@ -7,13 +7,13 @@ import { Server } from 'socket.io'
export class ZoneEventTileService { export class ZoneEventTileService {
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> { 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); const zone = await ZoneRepository.getById(teleport.toZoneId)
if (!zone) return; if (!zone) return
const oldZoneId = character.zoneId; const oldZoneId = character.zoneId
const newZoneId = teleport.toZoneId; const newZoneId = teleport.toZoneId
// Update character in database // Update character in database
await prisma.character.update({ await prisma.character.update({
@ -23,28 +23,28 @@ export class ZoneEventTileService {
positionX: teleport.toPositionX, positionX: teleport.toPositionX,
positionY: teleport.toPositionY positionY: teleport.toPositionY
} }
}); })
// Update local character object // Update local character object
character.zoneId = newZoneId; character.zoneId = newZoneId
character.positionX = teleport.toPositionX; character.positionX = teleport.toPositionX
character.positionY = teleport.toPositionY; character.positionY = teleport.toPositionY
// Atomic operation in ZoneManager // Atomic operation in ZoneManager
await ZoneManager.moveCharacterBetweenZones(oldZoneId, newZoneId, character as Character); await ZoneManager.moveCharacterBetweenZones(oldZoneId, newZoneId, character as Character)
// Emit events // Emit events
io.to(oldZoneId.toString()).emit('zone:character:leave', character.id); io.to(oldZoneId.toString()).emit('zone:character:leave', character.id)
io.to(newZoneId.toString()).emit('zone:character:join', character); io.to(newZoneId.toString()).emit('zone:character:join', character)
// Update socket rooms // Update socket rooms
socket.leave(oldZoneId.toString()); socket.leave(oldZoneId.toString())
socket.join(newZoneId.toString()); socket.join(newZoneId.toString())
// Send teleport information to the client // Send teleport information to the client
socket.emit('zone:teleport', { socket.emit('zone:teleport', {
zone, zone,
characters: ZoneManager.getCharactersInZone(zone.id) characters: ZoneManager.getCharactersInZone(zone.id)
}); })
} }
} }

View File

@ -2,16 +2,5 @@ import { ExtendedCharacter } from '../types'
import ZoneManager from '../../managers/zoneManager' import ZoneManager from '../../managers/zoneManager'
export class MovementValidator { 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
}
} }