#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:
parent
628b3bf1fa
commit
d4e0cbe398
12
package-lock.json
generated
12
package-lock.json
generated
@ -949,9 +949,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.24.0",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.24.0.tgz",
|
||||
"integrity": "sha512-rNWOg4opfHOhZjWWr1aIjfw2nUFB91F9qwIT49CdRypL4FznmHAqamTnw2EcZlj2KeFswV50tisZwq/h1yMUAw==",
|
||||
"version": "5.25.6",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.25.6.tgz",
|
||||
"integrity": "sha512-jxpa/DB02V20CqBAgyqpQazT630CJm0r4fky8EchH3mcJAomRtKXLS6tRA0J8tb29BDGlr/LXhlUuZwdBJBSdA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron-parser": "^4.6.0",
|
||||
@ -2064,9 +2064,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
||||
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
@ -1,3 +1,14 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE `World` (
|
||||
`date` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
`isRainEnabled` BOOLEAN NOT NULL DEFAULT false,
|
||||
`rainPercentage` INTEGER NOT NULL DEFAULT 0,
|
||||
`isFogEnabled` BOOLEAN NOT NULL DEFAULT false,
|
||||
`fogDensity` INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
UNIQUE INDEX `World_date_key`(`date`)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE `Chat` (
|
||||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
7
prisma/schema/game.prisma
Normal file
7
prisma/schema/game.prisma
Normal file
@ -0,0 +1,7 @@
|
||||
model World {
|
||||
date DateTime @unique @default(now())
|
||||
isRainEnabled Boolean @default(false)
|
||||
rainPercentage Int @default(0)
|
||||
isFogEnabled Boolean @default(false)
|
||||
fogDensity Int @default(0)
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import { ExtendedCharacter, TSocket } from '../utilities/types'
|
||||
import { Zone } from '@prisma/client'
|
||||
import prisma from '../utilities/prisma'
|
||||
|
||||
class CharacterManager {
|
||||
private characters!: ExtendedCharacter[]
|
||||
|
||||
public async boot() {
|
||||
this.characters = []
|
||||
}
|
||||
|
||||
public initCharacter(character: ExtendedCharacter) {
|
||||
this.characters = [...this.characters, character]
|
||||
}
|
||||
|
||||
public async removeCharacter(character: ExtendedCharacter) {
|
||||
await prisma.character.update({
|
||||
where: { id: character.id },
|
||||
data: {
|
||||
positionX: character.positionX,
|
||||
positionY: character.positionY,
|
||||
rotation: character.rotation,
|
||||
zoneId: character.zoneId
|
||||
}
|
||||
})
|
||||
this.characters = this.characters.filter((x) => x.id !== character.id)
|
||||
}
|
||||
|
||||
public getCharacterFromSocket(socket: TSocket) {
|
||||
return this.characters.find((x) => x.id === socket?.characterId)
|
||||
}
|
||||
|
||||
public hasResetMovement(character: ExtendedCharacter) {
|
||||
return this.characters.find((x) => x.id === character.id)?.resetMovement
|
||||
}
|
||||
|
||||
public getCharactersInZone(zone: Zone) {
|
||||
return this.characters.filter((x) => x.zoneId === zone.id)
|
||||
}
|
||||
}
|
||||
|
||||
export default new CharacterManager()
|
@ -1,7 +1,7 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { appLogger } from '../utilities/logger'
|
||||
import { getRootPath } from '../utilities/storage'
|
||||
import { readJsonValue, setJsonValue } from '../utilities/json'
|
||||
import prisma from '../utilities/prisma'
|
||||
import worldService from '../services/worldService'
|
||||
|
||||
class DateManager {
|
||||
private static readonly GAME_SPEED = 8 // 24 game hours / 3 real hours
|
||||
@ -18,7 +18,6 @@ class DateManager {
|
||||
appLogger.info('Date manager loaded')
|
||||
}
|
||||
|
||||
// When a GM sets the time, update the current date and update the world file
|
||||
public async setTime(time: string): Promise<void> {
|
||||
try {
|
||||
let newDate: Date
|
||||
@ -45,8 +44,13 @@ class DateManager {
|
||||
|
||||
private async loadDate(): Promise<void> {
|
||||
try {
|
||||
const dateString = await readJsonValue<string>(this.getWorldFilePath(), 'date')
|
||||
this.currentDate = new Date(dateString)
|
||||
const world = await prisma.world.findFirst({
|
||||
orderBy: { date: 'desc' }
|
||||
})
|
||||
|
||||
if (world) {
|
||||
this.currentDate = world.date
|
||||
}
|
||||
} catch (error) {
|
||||
appLogger.error(`Failed to load date: ${error instanceof Error ? error.message : String(error)}`)
|
||||
this.currentDate = new Date() // Use current date as fallback
|
||||
@ -57,7 +61,7 @@ class DateManager {
|
||||
this.intervalId = setInterval(() => {
|
||||
this.advanceGameTime()
|
||||
this.emitDate()
|
||||
this.saveDate()
|
||||
void this.saveDate()
|
||||
}, DateManager.UPDATE_INTERVAL)
|
||||
}
|
||||
|
||||
@ -72,14 +76,22 @@ class DateManager {
|
||||
|
||||
private async saveDate(): Promise<void> {
|
||||
try {
|
||||
await setJsonValue(this.getWorldFilePath(), 'date', this.currentDate)
|
||||
await worldService.update({
|
||||
date: this.currentDate
|
||||
})
|
||||
} catch (error) {
|
||||
appLogger.error(`Failed to save date: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private getWorldFilePath(): string {
|
||||
return getRootPath('data', 'world.json')
|
||||
public cleanup(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentDate(): Date {
|
||||
return this.currentDate
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { appLogger } from '../utilities/logger'
|
||||
import { getRootPath } from '../utilities/storage'
|
||||
import { readJsonValue, setJsonValue } from '../utilities/json'
|
||||
import prisma from '../utilities/prisma'
|
||||
import worldService from '../services/worldService'
|
||||
|
||||
interface WeatherState {
|
||||
isRainEnabled: boolean
|
||||
@ -37,46 +37,48 @@ class WeatherManager {
|
||||
? Math.floor(Math.random() * 50) + 50 // 50-100%
|
||||
: 0
|
||||
|
||||
// Save weather
|
||||
await this.saveWeather()
|
||||
|
||||
// Emit weather
|
||||
this.emitWeather()
|
||||
}
|
||||
|
||||
public async toggleFog(): Promise<void> {
|
||||
this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled
|
||||
this.weatherState.fogDensity = this.weatherState.isFogEnabled
|
||||
? Math.random() * 0.7 + 0.3 // 0.3-1.0
|
||||
? Math.floor((Math.random() * 0.7 + 0.3) * 100) // Convert 0.3-1.0 to 30-100
|
||||
: 0
|
||||
|
||||
// Save weather
|
||||
await this.saveWeather()
|
||||
|
||||
// Emit weather
|
||||
this.emitWeather()
|
||||
}
|
||||
|
||||
private async loadWeather(): Promise<void> {
|
||||
try {
|
||||
this.weatherState.isRainEnabled = await readJsonValue<boolean>(this.getWorldFilePath(), 'isRainEnabled')
|
||||
this.weatherState.rainPercentage = await readJsonValue<number>(this.getWorldFilePath(), 'rainPercentage')
|
||||
this.weatherState.isFogEnabled = await readJsonValue<boolean>(this.getWorldFilePath(), 'isFogEnabled')
|
||||
this.weatherState.fogDensity = await readJsonValue<number>(this.getWorldFilePath(), 'fogDensity')
|
||||
const world = await prisma.world.findFirst({
|
||||
orderBy: { date: 'desc' }
|
||||
})
|
||||
|
||||
if (world) {
|
||||
this.weatherState = {
|
||||
isRainEnabled: world.isRainEnabled,
|
||||
rainPercentage: world.rainPercentage,
|
||||
isFogEnabled: world.isFogEnabled,
|
||||
fogDensity: world.fogDensity
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
appLogger.error(`Failed to load weather: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
public async getWeatherState(): Promise<WeatherState> {
|
||||
public getWeatherState(): WeatherState {
|
||||
return this.weatherState
|
||||
}
|
||||
|
||||
private startWeatherLoop(): void {
|
||||
this.intervalId = setInterval(() => {
|
||||
this.intervalId = setInterval(async () => {
|
||||
this.updateWeather()
|
||||
this.emitWeather()
|
||||
this.saveWeather().catch((error) => {
|
||||
await this.saveWeather().catch((error) => {
|
||||
appLogger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`)
|
||||
})
|
||||
}, WeatherManager.UPDATE_INTERVAL)
|
||||
@ -95,7 +97,7 @@ class WeatherManager {
|
||||
if (Math.random() < WeatherManager.FOG_CHANCE) {
|
||||
this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled
|
||||
this.weatherState.fogDensity = this.weatherState.isFogEnabled
|
||||
? Math.random() * 0.7 + 0.3 // 0.3-1.0
|
||||
? Math.floor((Math.random() * 0.7 + 0.3) * 100) // Convert 0.3-1.0 to 30-100
|
||||
: 0
|
||||
}
|
||||
}
|
||||
@ -106,20 +108,21 @@ class WeatherManager {
|
||||
|
||||
private async saveWeather(): Promise<void> {
|
||||
try {
|
||||
const promises = [
|
||||
await setJsonValue(this.getWorldFilePath(), 'isRainEnabled', this.weatherState.isRainEnabled),
|
||||
await setJsonValue(this.getWorldFilePath(), 'rainPercentage', this.weatherState.rainPercentage),
|
||||
await setJsonValue(this.getWorldFilePath(), 'isFogEnabled', this.weatherState.isFogEnabled),
|
||||
await setJsonValue(this.getWorldFilePath(), 'fogDensity', this.weatherState.fogDensity)
|
||||
]
|
||||
await Promise.all(promises)
|
||||
await worldService.update({
|
||||
isRainEnabled: this.weatherState.isRainEnabled,
|
||||
rainPercentage: this.weatherState.rainPercentage,
|
||||
isFogEnabled: this.weatherState.isFogEnabled,
|
||||
fogDensity: this.weatherState.fogDensity
|
||||
})
|
||||
} catch (error) {
|
||||
appLogger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private getWorldFilePath(): string {
|
||||
return getRootPath('data', 'world.json')
|
||||
public cleanup(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,53 +3,53 @@ import ZoneRepository from '../repositories/zoneRepository'
|
||||
import ZoneService from '../services/zoneService'
|
||||
import LoadedZone from '../models/loadedZone'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
import ZoneCharacter from '../models/zoneCharacter'
|
||||
|
||||
class ZoneManager {
|
||||
private loadedZones: LoadedZone[] = []
|
||||
private readonly zones = new Map<number, LoadedZone>()
|
||||
|
||||
// Method to initialize zoneEditor manager
|
||||
public async boot() {
|
||||
public async boot(): Promise<void> {
|
||||
// Create first zone if it doesn't exist
|
||||
if (!(await ZoneRepository.getById(1))) {
|
||||
const zoneService = new ZoneService()
|
||||
await zoneService.createDemoZone()
|
||||
await new ZoneService().createDemoZone()
|
||||
}
|
||||
|
||||
const zones = await ZoneRepository.getAll()
|
||||
await Promise.all(zones.map((zone) => this.loadZone(zone)))
|
||||
|
||||
for (const zone of zones) {
|
||||
await this.loadZone(zone)
|
||||
}
|
||||
|
||||
gameLogger.info('Zone manager loaded')
|
||||
gameLogger.info(`Zone manager loaded with ${this.zones.size} zones`)
|
||||
}
|
||||
|
||||
// Method to handle individual zoneEditor loading
|
||||
public async loadZone(zone: Zone) {
|
||||
public async loadZone(zone: Zone): Promise<void> {
|
||||
const loadedZone = new LoadedZone(zone)
|
||||
this.loadedZones.push(loadedZone)
|
||||
this.zones.set(zone.id, loadedZone)
|
||||
gameLogger.info(`Zone ID ${zone.id} loaded`)
|
||||
}
|
||||
|
||||
// Method to handle individual zoneEditor unloading
|
||||
public unloadZone(zoneId: number) {
|
||||
this.loadedZones = this.loadedZones.filter((loadedZone) => loadedZone.getZone().id !== zoneId)
|
||||
public unloadZone(zoneId: number): void {
|
||||
this.zones.delete(zoneId)
|
||||
gameLogger.info(`Zone ID ${zoneId} unloaded`)
|
||||
}
|
||||
|
||||
// Getter for loaded zones
|
||||
public getLoadedZones(): LoadedZone[] {
|
||||
return this.loadedZones
|
||||
return Array.from(this.zones.values())
|
||||
}
|
||||
|
||||
// Getter for zone by id
|
||||
public getZoneById(zoneId: number): LoadedZone | undefined {
|
||||
return this.loadedZones.find((loadedZone) => loadedZone.getZone().id === zoneId)
|
||||
return this.zones.get(zoneId)
|
||||
}
|
||||
}
|
||||
|
||||
export interface ZoneAssets {
|
||||
tiles: string[]
|
||||
objects: string[]
|
||||
public getCharacter(characterId: number): ZoneCharacter | undefined {
|
||||
for (const zone of this.zones.values()) {
|
||||
const character = zone.getCharactersInZone().find((char) => char.character.id === characterId)
|
||||
if (character) return character
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
public removeCharacter(characterId: number): void {
|
||||
this.zones.forEach((zone) => zone.removeCharacter(characterId))
|
||||
}
|
||||
}
|
||||
|
||||
export default new ZoneManager()
|
||||
|
@ -37,7 +37,7 @@ export async function Authentication(socket: TSocket, next: any) {
|
||||
return next(new Error('Authentication error'))
|
||||
}
|
||||
|
||||
socket.user = (await UserRepository.getById(decoded.id)) as User
|
||||
socket.userId = decoded.id
|
||||
next()
|
||||
})
|
||||
} else {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Zone } from '@prisma/client'
|
||||
import { Character, Zone } from '@prisma/client'
|
||||
import zoneRepository from '../repositories/zoneRepository'
|
||||
import ZoneCharacter from './zoneCharacter'
|
||||
|
||||
class LoadedZone {
|
||||
private readonly zone: Zone
|
||||
// private readonly npcs: ZoneNPC[] = []
|
||||
private characters: ZoneCharacter[] = []
|
||||
|
||||
constructor(zone: Zone) {
|
||||
this.zone = zone
|
||||
@ -13,6 +14,27 @@ class LoadedZone {
|
||||
return this.zone
|
||||
}
|
||||
|
||||
public addCharacter(character: Character) {
|
||||
const zoneCharacter = new ZoneCharacter(character)
|
||||
this.characters.push(zoneCharacter)
|
||||
}
|
||||
|
||||
public async removeCharacter(id: number) {
|
||||
const zoneCharacter = this.getCharacterById(id)
|
||||
if (zoneCharacter) {
|
||||
await zoneCharacter.savePosition()
|
||||
this.characters = this.characters.filter((c) => c.character.id !== id)
|
||||
}
|
||||
}
|
||||
|
||||
public getCharacterById(id: number): ZoneCharacter | undefined {
|
||||
return this.characters.find((c) => c.character.id === id)
|
||||
}
|
||||
|
||||
public getCharactersInZone(): ZoneCharacter[] {
|
||||
return this.characters
|
||||
}
|
||||
|
||||
public async getGrid(): Promise<number[][]> {
|
||||
let grid: number[][] = Array.from({ length: this.zone.height }, () => Array.from({ length: this.zone.width }, () => 0))
|
||||
|
||||
@ -27,20 +49,6 @@ class LoadedZone {
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
/**
|
||||
* @TODO: Implement this
|
||||
* @param position
|
||||
*/
|
||||
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
|
||||
|
25
src/models/zoneCharacter.ts
Normal file
25
src/models/zoneCharacter.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { Character } from '@prisma/client'
|
||||
import prisma from '../utilities/prisma'
|
||||
|
||||
class ZoneCharacter {
|
||||
public readonly character: Character
|
||||
public isMoving: boolean = false
|
||||
|
||||
constructor(character: Character) {
|
||||
this.character = character
|
||||
}
|
||||
|
||||
public async savePosition() {
|
||||
await prisma.character.update({
|
||||
where: { id: this.character.id },
|
||||
data: {
|
||||
positionX: this.character.positionX,
|
||||
positionY: this.character.positionY,
|
||||
rotation: this.character.rotation,
|
||||
zoneId: this.character.zoneId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default ZoneCharacter
|
@ -1,5 +1,6 @@
|
||||
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
||||
import { Character } from '@prisma/client'
|
||||
import { appLogger } from '../utilities/logger'
|
||||
|
||||
class CharacterRepository {
|
||||
async getByUserId(userId: number): Promise<Character[] | null> {
|
||||
@ -19,7 +20,8 @@ class CharacterRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get character by user ID: ${error.message}`)
|
||||
appLogger.error(`Failed to get character by user ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +43,8 @@ class CharacterRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get character by user ID and character ID: ${error.message}`)
|
||||
appLogger.error(`Failed to get character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +65,8 @@ class CharacterRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get character by ID: ${error.message}`)
|
||||
appLogger.error(`Failed to get character by ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +83,8 @@ class CharacterRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to update character: ${error.message}`)
|
||||
appLogger.error(`Failed to update character: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +98,8 @@ class CharacterRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to delete character by user ID and character ID: ${error.message}`)
|
||||
appLogger.error(`Failed to delete character by user ID and character ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +120,8 @@ class CharacterRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get character by name: ${error.message}`)
|
||||
appLogger.error(`Failed to get character by name: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
||||
import prisma from '../utilities/prisma'
|
||||
import { appLogger } from '../utilities/logger' // Import the global Prisma instance
|
||||
|
||||
class PasswordResetTokenRepository {
|
||||
async getById(id: number): Promise<any> {
|
||||
@ -10,7 +11,7 @@ class PasswordResetTokenRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get password reset token by ID: ${error.message}`)
|
||||
appLogger.error(`Failed to get password reset token by ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,7 +24,7 @@ class PasswordResetTokenRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get password reset token by user ID: ${error.message}`)
|
||||
appLogger.error(`Failed to get password reset token by user ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,7 +37,7 @@ class PasswordResetTokenRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get password reset token by token: ${error.message}`)
|
||||
appLogger.error(`Failed to get password reset token by token: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
||||
import { User } from '@prisma/client'
|
||||
import { appLogger } from '../utilities/logger'
|
||||
|
||||
class UserRepository {
|
||||
async getById(id: number): Promise<User | null> {
|
||||
@ -11,7 +12,8 @@ class UserRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get user by ID: ${error.message}`)
|
||||
appLogger.error(`Failed to get user by ID: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,7 +26,8 @@ class UserRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get user by username: ${error.message}`)
|
||||
appLogger.error(`Failed to get user by username: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +40,8 @@ class UserRepository {
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
throw new Error(`Failed to get user by email: ${error.message}`)
|
||||
appLogger.error(`Failed to get user by email: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
src/repositories/worldRepository.ts
Normal file
19
src/repositories/worldRepository.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import prisma from '../utilities/prisma' // Import the global Prisma instance
|
||||
import { World } from '@prisma/client'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
|
||||
class WorldRepository {
|
||||
async getFirst(): Promise<World | null> {
|
||||
try {
|
||||
return await prisma.world.findFirst({
|
||||
orderBy: { date: 'desc' }
|
||||
})
|
||||
} catch (error: any) {
|
||||
// Handle error
|
||||
gameLogger.error(`Failed to get first world: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorldRepository()
|
@ -1,20 +1,9 @@
|
||||
import { Zone, ZoneEventTile, ZoneEventTileType, ZoneObject } from '@prisma/client'
|
||||
import prisma from '../utilities/prisma'
|
||||
import { ZoneEventTileWithTeleport } from '../socketEvents/zone/characterMove'
|
||||
import { ZoneEventTileWithTeleport } from '../utilities/types'
|
||||
import { appLogger } from '../utilities/logger'
|
||||
import { AssetData } from '../utilities/types'
|
||||
import tileRepository from './tileRepository'
|
||||
|
||||
class ZoneRepository {
|
||||
async getFirst(): Promise<Zone | null> {
|
||||
try {
|
||||
return await prisma.zone.findFirst()
|
||||
} catch (error: any) {
|
||||
appLogger.error(`Failed to get first zone: ${error.message}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async getAll(): Promise<Zone[]> {
|
||||
try {
|
||||
return await prisma.zone.findMany()
|
||||
|
@ -13,7 +13,6 @@ import { appLogger, watchLogs } from './utilities/logger'
|
||||
import ZoneManager from './managers/zoneManager'
|
||||
import UserManager from './managers/userManager'
|
||||
import CommandManager from './managers/commandManager'
|
||||
import CharacterManager from './managers/characterManager'
|
||||
import QueueManager from './managers/queueManager'
|
||||
import DateManager from './managers/dateManager'
|
||||
import WeatherManager from './managers/weatherManager'
|
||||
@ -30,7 +29,7 @@ export class Server {
|
||||
this.app = express()
|
||||
this.app.use(
|
||||
cors({
|
||||
origin: config.CLIENT_URL
|
||||
origin: config.CLIENT_URL // Allow CORS from the client URL
|
||||
})
|
||||
)
|
||||
this.app.use(express.json())
|
||||
@ -81,9 +80,6 @@ export class Server {
|
||||
// Load zoneEditor manager
|
||||
await ZoneManager.boot()
|
||||
|
||||
// Load character manager
|
||||
await CharacterManager.boot()
|
||||
|
||||
// Load command manager
|
||||
await CommandManager.boot(this.io)
|
||||
|
||||
|
@ -1,43 +1,57 @@
|
||||
import { ExtendedCharacter } from '../../utilities/types'
|
||||
import { AStar } from '../../utilities/character/aStar'
|
||||
import ZoneManager from '../../managers/zoneManager'
|
||||
import Rotation from '../../utilities/character/rotation'
|
||||
import { gameLogger } from '../../utilities/logger'
|
||||
import { Character } from '@prisma/client'
|
||||
|
||||
interface Position {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export class CharacterMoveService {
|
||||
public updatePosition(character: ExtendedCharacter, position: { x: number; y: number }, newZoneId?: number) {
|
||||
private static readonly MOVEMENT_DELAY_MS = 250
|
||||
|
||||
public updatePosition(character: Character, position: Position, newZoneId?: number): void {
|
||||
if (!this.isValidPosition(position)) {
|
||||
gameLogger.error(`Invalid position coordinates: ${position.x}, ${position.y}`)
|
||||
}
|
||||
|
||||
Object.assign(character, {
|
||||
positionX: position.x,
|
||||
positionY: position.y,
|
||||
rotation: Rotation.calculate(character.positionX, character.positionY, position.x, position.y),
|
||||
zoneId: newZoneId || character.zoneId
|
||||
zoneId: newZoneId ?? character.zoneId
|
||||
})
|
||||
|
||||
// await prisma.character.update({
|
||||
// where: { id: character.id },
|
||||
// data: {
|
||||
// positionX: position.x,
|
||||
// positionY: position.y,
|
||||
// rotation: character.rotation,
|
||||
// zoneId: newZoneId
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
public async calculatePath(character: ExtendedCharacter, targetX: number, targetY: number): Promise<Array<{ x: number; y: number }> | null> {
|
||||
const grid = await ZoneManager.getZoneById(character.zoneId)?.getGrid()
|
||||
public async calculatePath(character: Character, targetX: number, targetY: number): Promise<Position[] | null> {
|
||||
const zone = ZoneManager.getZoneById(character.zoneId)
|
||||
const grid = await zone?.getGrid()
|
||||
|
||||
if (!grid?.length) {
|
||||
gameLogger.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) }
|
||||
const start: Position = {
|
||||
x: Math.floor(character.positionX),
|
||||
y: Math.floor(character.positionY)
|
||||
}
|
||||
|
||||
const end: Position = {
|
||||
x: Math.floor(targetX),
|
||||
y: Math.floor(targetY)
|
||||
}
|
||||
|
||||
return AStar.findPath(start, end, grid)
|
||||
}
|
||||
|
||||
public async applyMovementDelay(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250)) // 250ms delay between steps
|
||||
await new Promise((resolve) => setTimeout(resolve, CharacterMoveService.MOVEMENT_DELAY_MS))
|
||||
}
|
||||
|
||||
private isValidPosition(position: Position): boolean {
|
||||
return Number.isFinite(position.x) && Number.isFinite(position.y) && position.x >= 0 && position.y >= 0
|
||||
}
|
||||
}
|
||||
|
37
src/services/worldService.ts
Normal file
37
src/services/worldService.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import prisma from '../utilities/prisma'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
import { World } from '@prisma/client'
|
||||
import WorldRepository from '../repositories/worldRepository'
|
||||
|
||||
class WorldService {
|
||||
async update(worldData: Partial<World>): Promise<boolean> {
|
||||
try {
|
||||
const currentWorld = await WorldRepository.getFirst()
|
||||
if (!currentWorld) {
|
||||
// If no world exists, create first record
|
||||
await prisma.world.create({
|
||||
data: {
|
||||
...worldData,
|
||||
date: worldData.date || new Date()
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
// Update existing world using its date as unique identifier
|
||||
await prisma.world.update({
|
||||
where: {
|
||||
date: currentWorld.date
|
||||
},
|
||||
data: worldData
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error: any) {
|
||||
gameLogger.error(`Failed to update world: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorldService()
|
@ -3,7 +3,7 @@ import prisma from '../utilities/prisma'
|
||||
import ZoneRepository from '../repositories/zoneRepository'
|
||||
import { ZoneEventTileTeleport } from '@prisma/client'
|
||||
import { Server } from 'socket.io'
|
||||
import CharacterManager from '../managers/characterManager'
|
||||
import ZoneManager from '../managers/zoneManager'
|
||||
|
||||
export class ZoneEventTileService {
|
||||
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> {
|
||||
@ -12,8 +12,6 @@ export class ZoneEventTileService {
|
||||
const zone = await ZoneRepository.getById(teleport.toZoneId)
|
||||
if (!zone) return
|
||||
|
||||
// CharacterManager.moveCharacterBetweenZones(character, zone)
|
||||
|
||||
const oldZoneId = character.zoneId
|
||||
const newZoneId = teleport.toZoneId
|
||||
|
||||
@ -46,7 +44,7 @@ export class ZoneEventTileService {
|
||||
// Send teleport information to the client
|
||||
socket.emit('zone:character:teleport', {
|
||||
zone,
|
||||
characters: CharacterManager.getCharactersInZone(zone)
|
||||
characters: ZoneManager.getZoneById(zone.id)?.getCharactersInZone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,84 +1,38 @@
|
||||
import prisma from '../utilities/prisma'
|
||||
import { AssetData } from '../utilities/types'
|
||||
import tileRepository from '../repositories/tileRepository'
|
||||
import zoneRepository from '../repositories/zoneRepository'
|
||||
import { Object, Zone, ZoneObject } from '@prisma/client'
|
||||
|
||||
type getZoneAsetsZoneType = Zone & {
|
||||
zoneObjects: (ZoneObject & {
|
||||
object: Object
|
||||
})[]
|
||||
}
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
|
||||
class ZoneService {
|
||||
async createDemoZone(): Promise<boolean> {
|
||||
const tiles = [
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile']
|
||||
]
|
||||
try {
|
||||
const tiles = [
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile']
|
||||
]
|
||||
|
||||
await prisma.zone.create({
|
||||
data: {
|
||||
name: 'Demo zone',
|
||||
width: 10,
|
||||
height: 10,
|
||||
tiles
|
||||
}
|
||||
})
|
||||
await prisma.zone.create({
|
||||
data: {
|
||||
name: 'Demo zone',
|
||||
width: 10,
|
||||
height: 10,
|
||||
tiles
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Demo zone created.')
|
||||
return true
|
||||
}
|
||||
gameLogger.info('Demo zone created.')
|
||||
|
||||
async getZoneAssets(zone: getZoneAsetsZoneType): Promise<AssetData[]> {
|
||||
const assets: AssetData[] = []
|
||||
|
||||
// zone.tiles is prisma jsonvalue
|
||||
let tiles = JSON.parse(JSON.stringify(zone.tiles))
|
||||
tiles = [...new Set(tiles.flat())]
|
||||
|
||||
// Add tile assets
|
||||
for (const tile of tiles) {
|
||||
const tileInfo = await tileRepository.getById(tile)
|
||||
if (!tileInfo) continue
|
||||
|
||||
assets.push({
|
||||
key: tileInfo.id,
|
||||
data: '/assets/tiles/' + tileInfo.id + '.png',
|
||||
group: 'tiles',
|
||||
updatedAt: tileInfo?.updatedAt || new Date()
|
||||
} as AssetData)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
gameLogger.error(`Failed to create demo zone: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return false
|
||||
}
|
||||
|
||||
// Add object assets
|
||||
for (const zoneObject of zone.zoneObjects) {
|
||||
if (!zoneObject.object) continue
|
||||
|
||||
assets.push({
|
||||
key: zoneObject.object.id,
|
||||
data: '/assets/objects/' + zoneObject.object.id + '.png',
|
||||
group: 'objects',
|
||||
updatedAt: zoneObject.object.updatedAt || new Date()
|
||||
} as AssetData)
|
||||
}
|
||||
|
||||
// Filter out duplicate assets
|
||||
return assets.reduce((acc: AssetData[], current) => {
|
||||
const x = acc.find((item) => item.key === current.key && item.group === current.group)
|
||||
if (!x) {
|
||||
return acc.concat([current])
|
||||
} else {
|
||||
return acc
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { TSocket } from '../../utilities/types'
|
||||
import CharacterRepository from '../../repositories/characterRepository'
|
||||
|
||||
type SocketResponseT = {
|
||||
character_id: number
|
||||
characterId: number
|
||||
}
|
||||
|
||||
export default class CharacterConnectEvent {
|
||||
@ -19,7 +19,7 @@ export default class CharacterConnectEvent {
|
||||
private async handleCharacterConnect(data: SocketResponseT): Promise<void> {
|
||||
console.log('character:connect requested', data)
|
||||
try {
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket?.user?.id as number, data.character_id)
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket?.userId!, data.characterId!)
|
||||
if (!character) return
|
||||
|
||||
this.socket.characterId = character.id
|
||||
|
@ -23,7 +23,7 @@ export default class CharacterCreateEvent {
|
||||
try {
|
||||
data = ZCharacterCreate.parse(data)
|
||||
|
||||
const user_id = this.socket.user?.id as number
|
||||
const user_id = this.socket.userId!
|
||||
|
||||
// Check if character name already exists
|
||||
const characterExists = await CharacterRepository.getByName(data.name)
|
||||
|
@ -4,7 +4,7 @@ import { Character, Zone } from '@prisma/client'
|
||||
import CharacterRepository from '../../repositories/characterRepository'
|
||||
|
||||
type TypePayload = {
|
||||
character_id: number
|
||||
characterId: number
|
||||
}
|
||||
|
||||
type TypeResponse = {
|
||||
@ -23,12 +23,10 @@ export default class CharacterDeleteEvent {
|
||||
}
|
||||
|
||||
private async handleCharacterDelete(data: TypePayload, callback: (response: TypeResponse) => void): Promise<any> {
|
||||
// zod validate
|
||||
try {
|
||||
await CharacterRepository.deleteByUserIdAndId(this.socket.user?.id as number, data.character_id as number)
|
||||
await CharacterRepository.deleteByUserIdAndId(this.socket.userId!, data.characterId!)
|
||||
|
||||
const user_id = this.socket.user?.id as number
|
||||
const characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
|
||||
const characters: Character[] = (await CharacterRepository.getByUserId(this.socket.userId!)) as Character[]
|
||||
|
||||
this.socket.emit('character:list', characters)
|
||||
} catch (error: any) {
|
||||
|
@ -2,6 +2,7 @@ import { Socket, Server } from 'socket.io'
|
||||
import { TSocket } from '../../utilities/types'
|
||||
import { Character } from '@prisma/client'
|
||||
import CharacterRepository from '../../repositories/characterRepository'
|
||||
import { gameLogger } from '../../utilities/logger'
|
||||
|
||||
export default class CharacterListEvent {
|
||||
constructor(
|
||||
@ -15,12 +16,10 @@ export default class CharacterListEvent {
|
||||
|
||||
private async handleCharacterList(data: any): Promise<void> {
|
||||
try {
|
||||
console.log('character:list requested')
|
||||
const user_id = this.socket.user?.id as number
|
||||
const characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
|
||||
const characters: Character[] = (await CharacterRepository.getByUserId(this.socket.userId!)) as Character[]
|
||||
this.socket.emit('character:list', characters)
|
||||
} catch (error: any) {
|
||||
console.log('character:list error', error)
|
||||
gameLogger.error('character:list error', error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export default class AlertCommandEvent {
|
||||
}
|
||||
|
||||
// Check if character exists
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.user?.id as number, this.socket.characterId as number)
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.userId!, this.socket.characterId!)
|
||||
if (!character) {
|
||||
gameLogger.error('chat:alert_command error', 'Character not found')
|
||||
callback(false)
|
||||
|
@ -26,7 +26,7 @@ export default class SetTimeCommand {
|
||||
}
|
||||
|
||||
// Check if character exists
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.user?.id as number, this.socket.characterId as number)
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.userId!, this.socket.characterId!)
|
||||
if (!character) {
|
||||
gameLogger.error('chat:alert_command error', 'Character not found')
|
||||
callback(false)
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { ExtendedCharacter, TSocket } from '../../../utilities/types'
|
||||
import { TSocket } from '../../../utilities/types'
|
||||
import { getArgs, isCommand } from '../../../utilities/chat'
|
||||
import ZoneRepository from '../../../repositories/zoneRepository'
|
||||
import CharacterManager from '../../../managers/characterManager'
|
||||
import { gameLogger, gameMasterLogger } from '../../../utilities/logger'
|
||||
import CharacterRepository from '../../../repositories/characterRepository'
|
||||
import ZoneManager from '../../../managers/zoneManager'
|
||||
import ZoneCharacter from '../../../models/zoneCharacter'
|
||||
|
||||
type TypePayload = {
|
||||
message: string
|
||||
@ -23,13 +23,15 @@ export default class TeleportCommandEvent {
|
||||
private async handleTeleportCommand(data: TypePayload, callback: (response: boolean) => void): Promise<void> {
|
||||
try {
|
||||
// Check if character exists
|
||||
const character = (await CharacterRepository.getByUserAndId(this.socket.user?.id as number, this.socket.characterId as number)) as ExtendedCharacter
|
||||
if (!character) {
|
||||
gameLogger.error('chat:alert_command error', 'Character not found')
|
||||
const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!)
|
||||
if (!zoneCharacter) {
|
||||
gameLogger.error('chat:send_message error', 'Character not found')
|
||||
callback(false)
|
||||
return
|
||||
}
|
||||
|
||||
const character = zoneCharacter.character
|
||||
|
||||
// Check if the user is the GM
|
||||
if (character.role !== 'gm') {
|
||||
gameLogger.info(`User ${character.id} tried to set time but is not a game master.`)
|
||||
@ -75,11 +77,11 @@ export default class TeleportCommandEvent {
|
||||
character.positionX = 0
|
||||
character.positionY = 0
|
||||
|
||||
character.resetMovement = true
|
||||
zoneCharacter.isMoving = false
|
||||
|
||||
this.socket.emit('zone:character:teleport', {
|
||||
zone,
|
||||
characters: CharacterManager.getCharactersInZone(zone)
|
||||
characters: ZoneManager.getZoneById(zone.id)?.getCharactersInZone()
|
||||
})
|
||||
|
||||
this.socket.emit('notification', { title: 'Server message', message: `You have been teleported to ${zone.name}` })
|
||||
|
@ -26,7 +26,7 @@ export default class ToggleFogCommand {
|
||||
}
|
||||
|
||||
// Check if character exists
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.user?.id as number, this.socket.characterId as number)
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.userId!, this.socket.characterId!)
|
||||
if (!character) {
|
||||
gameLogger.error('chat:alert_command error', 'Character not found')
|
||||
callback(false)
|
||||
|
@ -26,7 +26,7 @@ export default class ToggleRainCommand {
|
||||
}
|
||||
|
||||
// Check if character exists
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.user?.id as number, this.socket.characterId as number)
|
||||
const character = await CharacterRepository.getByUserAndId(this.socket.userId!, this.socket.characterId!)
|
||||
if (!character) {
|
||||
gameLogger.error('chat:alert_command error', 'Character not found')
|
||||
callback(false)
|
||||
|
@ -3,7 +3,7 @@ import { TSocket } from '../../utilities/types'
|
||||
import ZoneRepository from '../../repositories/zoneRepository'
|
||||
import { isCommand } from '../../utilities/chat'
|
||||
import { gameLogger } from '../../utilities/logger'
|
||||
import CharacterManager from '../../managers/characterManager'
|
||||
import ZoneManager from '../../managers/zoneManager'
|
||||
|
||||
type TypePayload = {
|
||||
message: string
|
||||
@ -26,13 +26,15 @@ export default class ChatMessageEvent {
|
||||
return
|
||||
}
|
||||
|
||||
const character = CharacterManager.getCharacterFromSocket(this.socket)
|
||||
if (!character) {
|
||||
const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!)
|
||||
if (!zoneCharacter) {
|
||||
gameLogger.error('chat:send_message error', 'Character not found')
|
||||
callback(false)
|
||||
return
|
||||
}
|
||||
|
||||
const character = zoneCharacter.character
|
||||
|
||||
const zone = await ZoneRepository.getById(character.zoneId)
|
||||
if (!zone) {
|
||||
gameLogger.error('chat:send_message error', 'Zone not found')
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { TSocket } from '../utilities/types'
|
||||
import CharacterManager from '../managers/characterManager'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
import ZoneManager from '../managers/zoneManager'
|
||||
|
||||
export default class DisconnectEvent {
|
||||
constructor(
|
||||
@ -10,31 +10,34 @@ export default class DisconnectEvent {
|
||||
) {}
|
||||
|
||||
public listen(): void {
|
||||
this.socket.on('disconnect', this.handleDisconnect.bind(this))
|
||||
this.socket.on('disconnect', this.handleEvent.bind(this))
|
||||
}
|
||||
|
||||
private async handleDisconnect(data: any): Promise<void> {
|
||||
private async handleEvent(data: any): Promise<void> {
|
||||
try {
|
||||
if (!this.socket.user) {
|
||||
if (!this.socket.userId) {
|
||||
gameLogger.info('User disconnected but had no user set')
|
||||
return
|
||||
}
|
||||
|
||||
this.io.emit('user:disconnect', this.socket.user.id)
|
||||
this.io.emit('user:disconnect', this.socket.userId)
|
||||
|
||||
const character = CharacterManager.getCharacterFromSocket(this.socket)
|
||||
|
||||
if (!character) {
|
||||
const zoneCharacter = ZoneManager.getCharacter(this.socket.characterId!)
|
||||
if (!zoneCharacter) {
|
||||
gameLogger.info('User disconnected but had no character set')
|
||||
return
|
||||
}
|
||||
|
||||
character.resetMovement = true
|
||||
const character = zoneCharacter.character
|
||||
|
||||
// Save character position and remove from zone
|
||||
zoneCharacter.isMoving = false
|
||||
await zoneCharacter.savePosition()
|
||||
ZoneManager.removeCharacter(this.socket.characterId!)
|
||||
|
||||
gameLogger.info('User disconnected along with their character')
|
||||
|
||||
await CharacterManager.removeCharacter(character)
|
||||
|
||||
// Inform other clients that the character has left
|
||||
this.io.in(character.zoneId.toString()).emit('zone:character:leave', character.id)
|
||||
this.io.emit('character:disconnect', character.id)
|
||||
} catch (error: any) {
|
||||
|
@ -17,7 +17,7 @@ export default class SpriteCreateEvent {
|
||||
|
||||
private async handleSpriteCreate(data: undefined, callback: (response: boolean) => void): Promise<void> {
|
||||
try {
|
||||
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||
const character = await characterRepository.getById(this.socket.characterId!)
|
||||
if (!character) return callback(false)
|
||||
|
||||
if (character.role !== 'gm') {
|
||||
|
@ -2,9 +2,9 @@ import { Server } from 'socket.io'
|
||||
import { TSocket } from '../../../../utilities/types'
|
||||
import fs from 'fs'
|
||||
import prisma from '../../../../utilities/prisma'
|
||||
import CharacterManager from '../../../../managers/characterManager'
|
||||
import { gameMasterLogger } from '../../../../utilities/logger'
|
||||
import { getPublicPath } from '../../../../utilities/storage'
|
||||
import CharacterRepository from '../../../../repositories/characterRepository'
|
||||
|
||||
type Payload = {
|
||||
id: string
|
||||
@ -25,7 +25,7 @@ export default class GMSpriteDeleteEvent {
|
||||
}
|
||||
|
||||
private async handleSpriteDelete(data: Payload, callback: (response: boolean) => void): Promise<void> {
|
||||
const character = CharacterManager.getCharacterFromSocket(this.socket)
|
||||
const character = await CharacterRepository.getById(this.socket.characterId!)
|
||||
if (character?.role !== 'gm') {
|
||||
return callback(false)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ export default class SpriteListEvent {
|
||||
}
|
||||
|
||||
private async handleSpriteList(data: any, callback: (response: Sprite[]) => void): Promise<void> {
|
||||
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||
const character = await characterRepository.getById(this.socket.characterId!)
|
||||
if (!character) return callback([])
|
||||
|
||||
if (character.role !== 'gm') {
|
||||
|
@ -4,8 +4,8 @@ import prisma from '../../../../utilities/prisma'
|
||||
import type { Prisma, SpriteAction } from '@prisma/client'
|
||||
import { writeFile, mkdir } from 'node:fs/promises'
|
||||
import sharp from 'sharp'
|
||||
import CharacterManager from '../../../../managers/characterManager'
|
||||
import { getPublicPath } from '../../../../utilities/storage'
|
||||
import CharacterRepository from '../../../../repositories/characterRepository'
|
||||
|
||||
type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & {
|
||||
sprites: string[]
|
||||
@ -38,7 +38,7 @@ export default class SpriteUpdateEvent {
|
||||
}
|
||||
|
||||
private async handleSpriteUpdate(data: Payload, callback: (success: boolean) => void): Promise<void> {
|
||||
const character = CharacterManager.getCharacterFromSocket(this.socket)
|
||||
const character = await CharacterRepository.getById(this.socket.characterId!)
|
||||
if (character?.role !== 'gm') {
|
||||
return callback(false)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { TSocket } from '../utilities/types'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
import UserRepository from '../repositories/userRepository'
|
||||
|
||||
export default class LoginEvent {
|
||||
constructor(
|
||||
@ -14,13 +15,13 @@ export default class LoginEvent {
|
||||
|
||||
private handleLogin(): void {
|
||||
try {
|
||||
if (!this.socket.user) {
|
||||
if (!this.socket.userId) {
|
||||
gameLogger.warn('Login attempt without user data')
|
||||
return
|
||||
}
|
||||
|
||||
this.socket.emit('logged_in', { user: this.socket.user })
|
||||
gameLogger.info(`User logged in: ${this.socket.user.id}`)
|
||||
this.socket.emit('logged_in', { user: UserRepository.getById(this.socket.userId) })
|
||||
gameLogger.info(`User logged in: ${this.socket.userId}`)
|
||||
} catch (error: any) {
|
||||
gameLogger.error('login error', error.message)
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { TSocket } from '../utilities/types'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
import WeatherManager from '../managers/weatherManager'
|
||||
import { TSocket } from '../../utilities/types'
|
||||
import { gameLogger } from '../../utilities/logger'
|
||||
import WeatherManager from '../../managers/weatherManager'
|
||||
|
||||
export default class Weather {
|
||||
constructor(
|
@ -1,49 +0,0 @@
|
||||
import * as fs from 'fs/promises'
|
||||
import { appLogger } from './logger'
|
||||
|
||||
export async function readJsonFile<T>(filePath: string): Promise<T> {
|
||||
try {
|
||||
const fileContent = await fs.readFile(filePath, 'utf-8')
|
||||
return JSON.parse(fileContent) as T
|
||||
} catch (error) {
|
||||
appLogger.error(`Error reading JSON file: ${error instanceof Error ? error.message : String(error)}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeJsonFile<T>(filePath: string, data: T): Promise<void> {
|
||||
try {
|
||||
const jsonString = JSON.stringify(data, null, 2)
|
||||
await fs.writeFile(filePath, jsonString, 'utf-8')
|
||||
} catch (error) {
|
||||
appLogger.error(`Error writing JSON file: ${error instanceof Error ? error.message : String(error)}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function readJsonValue<T>(filePath: string, paramPath: string): Promise<T> {
|
||||
try {
|
||||
const jsonContent = await readJsonFile<any>(filePath)
|
||||
const paramValue = paramPath.split('.').reduce((obj, key) => obj && obj[key], jsonContent)
|
||||
|
||||
if (paramValue === undefined) {
|
||||
throw new Error(`Parameter ${paramPath} not found in the JSON file`)
|
||||
}
|
||||
|
||||
return paramValue as T
|
||||
} catch (error) {
|
||||
appLogger.error(`Error reading JSON parameter: ${error instanceof Error ? error.message : String(error)}`)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function setJsonValue<T>(filePath: string, key: string, value: any): Promise<void> {
|
||||
try {
|
||||
const data = await readJsonFile<T>(filePath)
|
||||
const updatedData = { ...data, [key]: value }
|
||||
await writeJsonFile(filePath, updatedData)
|
||||
} catch (error) {
|
||||
appLogger.error(`Error setting JSON value: ${error instanceof Error ? error.message : String(error)}`)
|
||||
throw error
|
||||
}
|
||||
}
|
@ -32,13 +32,31 @@ const watchLogs = () => {
|
||||
LOG_TYPES.forEach((type) => {
|
||||
const logFile = getRootPath('logs', `${type}.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(`[${type}]\n${chunk.toString()}`)
|
||||
})
|
||||
// Get initial file size
|
||||
const stats = fs.statSync(logFile)
|
||||
let lastPosition = stats.size
|
||||
|
||||
fs.watch(logFile, (eventType) => {
|
||||
if (eventType !== 'change') {
|
||||
return
|
||||
}
|
||||
|
||||
fs.stat(logFile, (err, stats) => {
|
||||
if (err) return
|
||||
|
||||
if (stats.size > lastPosition) {
|
||||
const stream = fs.createReadStream(logFile, {
|
||||
start: lastPosition,
|
||||
end: stats.size
|
||||
})
|
||||
|
||||
stream.on('data', (chunk) => {
|
||||
console.log(`[${type}]\n${chunk.toString()}`)
|
||||
})
|
||||
|
||||
lastPosition = stats.size
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Socket } from 'socket.io'
|
||||
import { Character, User } from '@prisma/client'
|
||||
import { Character, User, ZoneEventTile, ZoneEventTileTeleport } from '@prisma/client'
|
||||
|
||||
export type TSocket = Socket & {
|
||||
user?: User
|
||||
userId?: number
|
||||
characterId?: number
|
||||
handshake?: {
|
||||
query?: {
|
||||
@ -18,7 +18,11 @@ export type TSocket = Socket & {
|
||||
|
||||
export type ExtendedCharacter = Character & {
|
||||
isMoving?: boolean
|
||||
resetMovement: boolean
|
||||
resetMovement?: boolean
|
||||
}
|
||||
|
||||
export type ZoneEventTileWithTeleport = ZoneEventTile & {
|
||||
teleport: ZoneEventTileTeleport
|
||||
}
|
||||
|
||||
export type AssetData = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user