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": {
"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"

View File

@ -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,16 +38,15 @@ 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')
return
}
if(character.isMoving && !character.resetMovement) {
character.resetMovement = true;
this.nextPath[character.id] = path;
if (character.isMoving && !character.resetMovement) {
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;
if (character.isMoving && character.resetMovement) {
character.isMoving = false
character.resetMovement = false
const nextPath = this.nextPath[character.id]
this.moveAlongPath(character, nextPath)
break
}
if(!character.isMoving) {
character.isMoving = true;
if (!character.isMoving) {
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> {

View File

@ -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}`);
}
}

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'
class ZoneCharacter {
export default class ZoneCharacter {
private readonly character: Character
private isMoving: boolean = false

View File

@ -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)
});
})
}
}

View File

@ -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
}
}