1
0
forked from noxious/server

#174: Refactor character manager into zoneManager for better DX, major refactor of time and weather system (data is stored in DB now instead of JSON file), npm update, npm format, many other improvements

This commit is contained in:
2024-11-13 13:21:01 +01:00
parent 628b3bf1fa
commit d4e0cbe398
43 changed files with 465 additions and 461 deletions

View File

@ -1,14 +1,16 @@
import { Server } from 'socket.io'
import { ExtendedCharacter, TSocket } from '../../utilities/types'
import { TSocket } from '../../utilities/types'
import ZoneRepository from '../../repositories/zoneRepository'
import { Character, Zone } from '@prisma/client'
import CharacterManager from '../../managers/characterManager'
import { Zone } from '@prisma/client'
import { gameLogger } from '../../utilities/logger'
import CharacterRepository from '../../repositories/characterRepository'
import ZoneManager from '../../managers/zoneManager'
import zoneCharacter from '../../models/zoneCharacter'
import zoneManager from '../../managers/zoneManager'
interface IResponse {
zone: Zone
characters: Character[]
characters: zoneCharacter[]
}
export default class CharacterJoinEvent {
@ -28,30 +30,42 @@ export default class CharacterJoinEvent {
return
}
const character = await CharacterRepository.getById(this.socket.characterId as number)
const character = await CharacterRepository.getById(this.socket.characterId)
if (!character) {
gameLogger.error('zone:character:join error', 'Character not found')
return
}
/**
* @TODO: If zone is not found, spawn back to the start
*/
const zone = await ZoneRepository.getById(character.zoneId)
if (!zone) {
gameLogger.error('zone:character:join error', 'Zone not found')
return
}
CharacterManager.initCharacter(character as ExtendedCharacter)
/**
* @TODO: If zone is not found, spawn back to the start
*/
const loadedZone = ZoneManager.getZoneById(zone.id)
if (!loadedZone) {
gameLogger.error('zone:character:join error', 'Loaded zone not found')
return
}
loadedZone.addCharacter(character)
this.socket.join(zone.id.toString())
// let other clients know of new character
this.io.to(zone.id.toString()).emit('zone:character:join', character)
// Let other clients know of new character
this.io.to(zone.id.toString()).emit('zone:character:join', zoneManager.getCharacter(character.id))
// Log
gameLogger.info(`User ${character.id} joined zone ${zone.id}`)
// send over zone and characters to socket
callback({ zone, characters: CharacterManager.getCharactersInZone(zone) })
// Send over zone and characters to socket
callback({ zone, characters: loadedZone.getCharactersInZone() })
} catch (error: any) {
gameLogger.error('zone:character:join error', error.message)
this.socket.disconnect()

View File

@ -1,8 +1,9 @@
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/types'
import ZoneRepository from '../../repositories/zoneRepository'
import CharacterManager from '../../managers/characterManager'
import { gameLogger } from '../../utilities/logger'
import ZoneManager from '../../managers/zoneManager'
import CharacterRepository from '../../repositories/characterRepository'
export default class ZoneLeaveEvent {
constructor(
@ -16,31 +17,41 @@ export default class ZoneLeaveEvent {
private async handleZoneLeave(): Promise<void> {
try {
const character = CharacterManager.getCharacterFromSocket(this.socket)
if (!this.socket.characterId) {
gameLogger.error('zone:character:join error', 'Zone requested but no character id set')
return
}
const character = await CharacterRepository.getById(this.socket.characterId)
if (!character) {
gameLogger.error('zone:character:leave error', 'Character not found')
return
}
if (!character.zoneId) {
gameLogger.error('zone:character:leave error', 'Character not in a zone')
gameLogger.error('zone:character:join error', 'Character not found')
return
}
/**
* @TODO: If zone is not found, spawn back to the start
*/
const zone = await ZoneRepository.getById(character.zoneId)
if (!zone) {
gameLogger.error('zone:character:leave error', 'Zone not found')
gameLogger.error('zone:character:join error', 'Zone not found')
return
}
const loadedZone = ZoneManager.getZoneById(zone.id)
if (!loadedZone) {
gameLogger.error('zone:character:join error', 'Loaded zone not found')
return
}
console.log('awee')
this.socket.leave(zone.id.toString())
// let other clients know of character leaving
this.io.to(zone.id.toString()).emit('zone:character:leave', character.id)
// remove character from zone manager
await CharacterManager.removeCharacter(character)
await loadedZone.removeCharacter(character.id)
gameLogger.info('zone:character:leave', `Character ${character.id} left zone ${zone.id}`)
} catch (error: any) {

View File

@ -1,77 +1,53 @@
import { Server } from 'socket.io'
import { TSocket, ExtendedCharacter } from '../../utilities/types'
import { TSocket, ZoneEventTileWithTeleport } from '../../utilities/types'
import { CharacterMoveService } from '../../services/character/characterMoveService'
import { ZoneEventTileService } from '../../services/zoneEventTileService'
import prisma from '../../utilities/prisma'
import { ZoneEventTile, ZoneEventTileTeleport } from '@prisma/client'
import Rotation from '../../utilities/character/rotation'
import CharacterManager from '../../managers/characterManager'
import { gameLogger } from '../../utilities/logger'
import QueueManager from '../../managers/queueManager'
export type ZoneEventTileWithTeleport = ZoneEventTile & {
teleport: ZoneEventTileTeleport
}
import ZoneManager from '../../managers/zoneManager'
import ZoneCharacter from '../../models/zoneCharacter'
export default class CharacterMove {
private characterMoveService: CharacterMoveService
private zoneEventTileService: ZoneEventTileService
private nextPath: { [index: number]: { x: number; y: number }[] } = []
private currentZoneId: { [index: number]: number } = []
private readonly characterMoveService = new CharacterMoveService()
private readonly zoneEventTileService = new ZoneEventTileService()
private nextPath = new Map<number, { x: number; y: number }[]>()
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {
this.characterMoveService = new CharacterMoveService()
this.zoneEventTileService = new ZoneEventTileService()
}
) {}
public listen(): void {
this.socket.on('character:initMove', this.handleCharacterMove.bind(this))
this.socket.on('character:move', this.handleCharacterMove.bind(this))
}
private async handleCharacterMove({ positionX, positionY }: { positionX: number; positionY: number }): Promise<void> {
let character = CharacterManager.getCharacterFromSocket(this.socket)
if (!character) {
gameLogger.error('character:move error', 'Character not found')
const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!)
if (!zoneCharacter?.character) {
gameLogger.error('character:move error', 'Character not found or not initialized')
return
}
if (!character) {
gameLogger.error('character:move error', 'character has not been initialized?')
return
}
const path = await this.characterMoveService.calculatePath(character, positionX, positionY)
const path = await this.characterMoveService.calculatePath(zoneCharacter.character, positionX, positionY)
if (!path) {
this.io.in(character.zoneId.toString()).emit('character:moveError', 'No valid path found')
this.io.in(zoneCharacter.character.zoneId.toString()).emit('character:moveError', 'No valid path found')
return
}
if (!character.isMoving && character.resetMovement) {
character.resetMovement = false
}
if (character.isMoving && !character.resetMovement) {
character.resetMovement = true
this.nextPath[character.id] = path
}
if (!character.isMoving && !character.resetMovement) {
character.isMoving = true
this.currentZoneId[character.id] = character.zoneId
await this.moveAlongPath(character, path)
if (!zoneCharacter.isMoving) {
zoneCharacter.isMoving = true
await this.moveAlongPath(zoneCharacter, path)
} else {
this.nextPath.set(zoneCharacter.character.id, path)
}
}
private async moveAlongPath(character: ExtendedCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
private async moveAlongPath(zoneCharacter: ZoneCharacter, path: Array<{ x: number; y: number }>): Promise<void> {
const { character } = zoneCharacter
for (let i = 0; i < path.length - 1; i++) {
const start = path[i]
const end = path[i + 1]
if (CharacterManager.hasResetMovement(character)) {
break
}
const [start, end] = [path[i], path[i + 1]]
character.rotation = Rotation.calculate(start.x, start.y, end.x, end.y)
const zoneEventTile = await prisma.zoneEventTile.findFirst({
@ -79,62 +55,44 @@ export default class CharacterMove {
zoneId: character.zoneId,
positionX: Math.floor(end.x),
positionY: Math.floor(end.y)
}
},
include: { teleport: true }
})
if (zoneEventTile) {
if (zoneEventTile.type === 'BLOCK') {
break
}
if (zoneEventTile.type === 'TELEPORT') {
const teleportTile = (await prisma.zoneEventTile.findFirst({
where: { id: zoneEventTile.id },
include: { teleport: true }
})) as ZoneEventTileWithTeleport
if (teleportTile) {
await this.handleZoneEventTile(teleportTile)
break
}
}
if (zoneEventTile?.type === 'BLOCK') break
if (zoneEventTile?.type === 'TELEPORT' && zoneEventTile.teleport) {
await this.handleZoneEventTile(zoneEventTile as ZoneEventTileWithTeleport)
break
}
this.characterMoveService.updatePosition(character, end)
this.io.in(character.zoneId.toString()).emit('character:move', character)
this.io.in(character.zoneId.toString()).emit('character:move', zoneCharacter)
await this.characterMoveService.applyMovementDelay()
}
if (CharacterManager.hasResetMovement(character)) {
character.resetMovement = false
if (this.currentZoneId[character.id] === character.zoneId) {
await this.moveAlongPath(character, this.nextPath[character.id])
} else {
delete this.currentZoneId[character.id]
character.isMoving = false
}
const nextPath = this.nextPath.get(character.id)
if (nextPath) {
this.nextPath.delete(character.id)
await this.moveAlongPath(zoneCharacter, nextPath)
} else {
this.finalizeMovement(character)
this.finalizeMovement(zoneCharacter)
}
}
private async handleZoneEventTile(zoneEventTile: ZoneEventTileWithTeleport): Promise<void> {
const character = CharacterManager.getCharacterFromSocket(this.socket)
if (!character) {
const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!)
if (!zoneCharacter) {
gameLogger.error('character:move error', 'Character not found')
return
}
const teleport = zoneEventTile.teleport
if (teleport) {
await this.zoneEventTileService.handleTeleport(this.io, this.socket, character, teleport)
return
if (zoneEventTile.teleport) {
await this.zoneEventTileService.handleTeleport(this.io, this.socket, zoneCharacter.character, zoneEventTile.teleport)
}
}
private finalizeMovement(character: ExtendedCharacter): void {
character.isMoving = false
this.io.in(character.zoneId.toString()).emit('character:move', character)
private finalizeMovement(zoneCharacter: ZoneCharacter): void {
zoneCharacter.isMoving = false
this.io.in(zoneCharacter.character.zoneId.toString()).emit('character:move', zoneCharacter)
}
}

View File

@ -0,0 +1,24 @@
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/types'
import { gameLogger } from '../../utilities/logger'
import WeatherManager from '../../managers/weatherManager'
export default class Weather {
constructor(
private readonly io: Server,
private readonly socket: TSocket
) {}
public listen(): void {
this.socket.on('weather', this.handleEvent.bind(this))
}
private async handleEvent(): Promise<void> {
try {
const weather = await WeatherManager.getWeatherState()
this.socket.emit('weather', weather)
} catch (error: any) {
gameLogger.error('error', error.message)
}
}
}