diff --git a/package.json b/package.json index 3e38dd8..1a3bbef 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "scripts": { "start": "npx prisma migrate deploy && node dist/server.js", - "dev": "nodemon --exec ts-node src/server.ts", + "dev": "nodemon --ignore 'data/*' --exec ts-node src/server.ts", "build": "tsc", "format": "prettier --write src/" }, diff --git a/src/commands/tiles.ts b/src/commands/tiles.ts index 02f2d39..8f9ba4f 100644 --- a/src/commands/tiles.ts +++ b/src/commands/tiles.ts @@ -2,7 +2,7 @@ import fs from 'fs' import sharp from 'sharp' import { commandLogger } from '../utilities/logger' import { Server } from 'socket.io' -import { getPublicPath } from '../utilities/files' +import { getPublicPath } from '../utilities/storage' import path from 'path' export default class TilesCommand { diff --git a/src/managers/commandManager.ts b/src/managers/commandManager.ts index 4c8a1a0..5d6f386 100644 --- a/src/managers/commandManager.ts +++ b/src/managers/commandManager.ts @@ -3,7 +3,7 @@ import * as fs from 'fs' import * as path from 'path' import { Server } from 'socket.io' import { commandLogger } from '../utilities/logger' -import { getAppPath } from '../utilities/files' +import { getAppPath } from '../utilities/storage' class CommandManager { private commands: Map = new Map() diff --git a/src/managers/datetimeManager.ts b/src/managers/datetimeManager.ts index 7dc2c0e..cd511c7 100644 --- a/src/managers/datetimeManager.ts +++ b/src/managers/datetimeManager.ts @@ -1,19 +1,21 @@ // src/managers/datetimeManager.ts -import fs from 'fs/promises' import { Server } from 'socket.io' import { appLogger } from '../utilities/logger' -import { createDir, doesPathExist, getRootPath } from '../utilities/files' +import { getRootPath } from '../utilities/storage' +import { readJsonValue, setJsonValue } from '../utilities/json' class DatetimeManager { - private static readonly GAME_SPEED = 24 / 3 // 24 hours / 3 hours = 8x speed - private static readonly UPDATE_INTERVAL = 1000 // Update every second for smooth second transitions + private static readonly GAME_SPEED = 8 // 24 game hours / 3 real hours + private static readonly UPDATE_INTERVAL = 1000 // 1 second private io: Server | null = null private intervalId: NodeJS.Timeout | null = null + private currentDateTime: Date = new Date() public async boot(io: Server): Promise { this.io = io + await this.loadDateTime() this.startDateTimeLoop() appLogger.info('Datetime manager loaded') } @@ -25,59 +27,47 @@ class DatetimeManager { } } - public async loadDateTime(): Promise { + private async loadDateTime(): Promise { try { - const datetimeFilePath = this.getDatetimeFilePath() - const content = await fs.readFile(datetimeFilePath, 'utf-8') - return new Date(content.trim()) + const datetimeString = await readJsonValue(this.getWorldFilePath(), 'datetime') + this.currentDateTime = new Date(datetimeString) } catch (error) { appLogger.error(`Failed to load datetime: ${error instanceof Error ? error.message : String(error)}`) - return new Date() // Use current date as fallback + this.currentDateTime = new Date() // Use current date as fallback } } private startDateTimeLoop(): void { - this.intervalId = setInterval(async () => { - const currentDateTime = await this.loadDateTime() - this.advanceGameTime(currentDateTime) - this.emitDateTime(currentDateTime) - this.saveDateTimeIfNeeded(currentDateTime) + this.intervalId = setInterval(() => { + this.advanceGameTime() + this.emitDateTime() + this.saveDateTime() }, DatetimeManager.UPDATE_INTERVAL) } - private advanceGameTime(currentDateTime: Date): void { - const advanceTime = (DatetimeManager.GAME_SPEED * DatetimeManager.UPDATE_INTERVAL) / 1000 * 1000 - currentDateTime.setTime(currentDateTime.getTime() + advanceTime) + private advanceGameTime(): void { + const advanceMilliseconds = DatetimeManager.GAME_SPEED * DatetimeManager.UPDATE_INTERVAL + this.currentDateTime = new Date(this.currentDateTime.getTime() + advanceMilliseconds) } - private emitDateTime(currentDateTime: Date): void { - this.io?.emit('datetime', this.formatDateTime(currentDateTime)) + private emitDateTime(): void { + this.io?.emit('datetime', this.formatDateTime(this.currentDateTime)) } private formatDateTime(date: Date): string { return date.toISOString().slice(0, 19).replace('T', ' ') } - private saveDateTimeIfNeeded(currentDateTime: Date): void { - if (currentDateTime.getMilliseconds() < DatetimeManager.UPDATE_INTERVAL) { - this.saveDateTime(currentDateTime) - } - } - - private async saveDateTime(currentDateTime: Date): Promise { + private async saveDateTime(): Promise { try { - const datetimeFilePath = this.getDatetimeFilePath() - await fs.writeFile(datetimeFilePath, this.formatDateTime(currentDateTime)) + await setJsonValue(this.getWorldFilePath(), 'datetime', this.formatDateTime(this.currentDateTime)) } catch (error) { appLogger.error(`Failed to save datetime: ${error instanceof Error ? error.message : String(error)}`) } } - private getDatetimeFilePath(): string { - if (!doesPathExist(getRootPath('data'))) { - createDir(getRootPath('data')) - } - return getRootPath('data', 'datetime.txt') + private getWorldFilePath(): string { + return getRootPath('data', 'world.json') } } diff --git a/src/managers/queueManager.ts b/src/managers/queueManager.ts index d05fe7d..27e226b 100644 --- a/src/managers/queueManager.ts +++ b/src/managers/queueManager.ts @@ -5,7 +5,7 @@ import { Server as SocketServer } from 'socket.io' import { TSocket } from '../utilities/types' import { queueLogger } from '../utilities/logger' import fs from 'fs' -import { getAppPath } from '../utilities/files' +import { getAppPath } from '../utilities/storage' class QueueManager { private connection!: IORedis diff --git a/src/server.ts b/src/server.ts index d9b1bf5..5a3a271 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ import fs from 'fs' import express, { Application } from 'express' import config from './utilities/config' -import { getAppPath } from './utilities/files' +import { getAppPath } from './utilities/storage' import { createServer as httpServer, Server as HTTPServer } from 'http' import { addHttpRoutes } from './utilities/http' import cors from 'cors' diff --git a/src/socketEvents/gameMaster/assetManager/object/remove.ts b/src/socketEvents/gameMaster/assetManager/object/remove.ts index 17d0ce9..883e577 100644 --- a/src/socketEvents/gameMaster/assetManager/object/remove.ts +++ b/src/socketEvents/gameMaster/assetManager/object/remove.ts @@ -3,7 +3,7 @@ import { Server } from 'socket.io' import { TSocket } from '../../../../utilities/types' import prisma from '../../../../utilities/prisma' import characterRepository from '../../../../repositories/characterRepository' -import { getPublicPath } from '../../../../utilities/files' +import { getPublicPath } from '../../../../utilities/storage' interface IPayload { object: string diff --git a/src/socketEvents/gameMaster/assetManager/object/upload.ts b/src/socketEvents/gameMaster/assetManager/object/upload.ts index c42ffae..7b196da 100644 --- a/src/socketEvents/gameMaster/assetManager/object/upload.ts +++ b/src/socketEvents/gameMaster/assetManager/object/upload.ts @@ -6,7 +6,7 @@ import prisma from '../../../../utilities/prisma' import sharp from 'sharp' import characterRepository from '../../../../repositories/characterRepository' import { gameMasterLogger } from '../../../../utilities/logger' -import { getPublicPath } from '../../../../utilities/files' +import { getPublicPath } from '../../../../utilities/storage' interface IObjectData { [key: string]: Buffer diff --git a/src/socketEvents/gameMaster/assetManager/sprite/create.ts b/src/socketEvents/gameMaster/assetManager/sprite/create.ts index 68ba746..b7447a7 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/create.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/create.ts @@ -3,7 +3,7 @@ import { TSocket } from '../../../../utilities/types' import fs from 'fs/promises' import prisma from '../../../../utilities/prisma' import characterRepository from '../../../../repositories/characterRepository' -import { getPublicPath } from '../../../../utilities/files' +import { getPublicPath } from '../../../../utilities/storage' export default class SpriteCreateEvent { constructor( diff --git a/src/socketEvents/gameMaster/assetManager/sprite/delete.ts b/src/socketEvents/gameMaster/assetManager/sprite/delete.ts index f346a8d..a97c2e3 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/delete.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/delete.ts @@ -4,7 +4,7 @@ import fs from 'fs' import prisma from '../../../../utilities/prisma' import CharacterManager from '../../../../managers/characterManager' import { gameMasterLogger } from '../../../../utilities/logger' -import { getPublicPath } from '../../../../utilities/files' +import { getPublicPath } from '../../../../utilities/storage' type Payload = { id: string diff --git a/src/socketEvents/gameMaster/assetManager/sprite/update.ts b/src/socketEvents/gameMaster/assetManager/sprite/update.ts index 5d15424..9800fe9 100644 --- a/src/socketEvents/gameMaster/assetManager/sprite/update.ts +++ b/src/socketEvents/gameMaster/assetManager/sprite/update.ts @@ -5,7 +5,7 @@ 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/files' +import { getPublicPath } from '../../../../utilities/storage' type SpriteActionInput = Omit & { sprites: string[] diff --git a/src/socketEvents/gameMaster/assetManager/tile/delete.ts b/src/socketEvents/gameMaster/assetManager/tile/delete.ts index 32becbe..1bf994a 100644 --- a/src/socketEvents/gameMaster/assetManager/tile/delete.ts +++ b/src/socketEvents/gameMaster/assetManager/tile/delete.ts @@ -4,7 +4,7 @@ import { TSocket } from '../../../../utilities/types' import prisma from '../../../../utilities/prisma' import characterRepository from '../../../../repositories/characterRepository' import { gameMasterLogger } from '../../../../utilities/logger' -import { getPublicPath } from '../../../../utilities/files' +import { getPublicPath } from '../../../../utilities/storage' type Payload = { id: string diff --git a/src/socketEvents/gameMaster/assetManager/tile/upload.ts b/src/socketEvents/gameMaster/assetManager/tile/upload.ts index beb8ea6..b14febf 100644 --- a/src/socketEvents/gameMaster/assetManager/tile/upload.ts +++ b/src/socketEvents/gameMaster/assetManager/tile/upload.ts @@ -5,7 +5,7 @@ import fs from 'fs/promises' import prisma from '../../../../utilities/prisma' import characterRepository from '../../../../repositories/characterRepository' import { gameMasterLogger } from '../../../../utilities/logger' -import { getPublicPath } from '../../../../utilities/files' +import { getPublicPath } from '../../../../utilities/storage' interface ITileData { [key: string]: Buffer diff --git a/src/utilities/http.ts b/src/utilities/http.ts index 1910678..2581d5a 100644 --- a/src/utilities/http.ts +++ b/src/utilities/http.ts @@ -12,7 +12,7 @@ import fs from 'fs' import zoneRepository from '../repositories/zoneRepository' import zoneManager from '../managers/zoneManager' import { httpLogger } from './logger' -import { getPublicPath } from './files' +import { getPublicPath } from './storage' async function addHttpRoutes(app: Application) { /** diff --git a/src/utilities/json.ts b/src/utilities/json.ts new file mode 100644 index 0000000..b7735bf --- /dev/null +++ b/src/utilities/json.ts @@ -0,0 +1,49 @@ +import * as fs from 'fs/promises'; +import { appLogger } from './logger'; + +export async function readJsonFile(filePath: string): Promise { + 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(filePath: string, data: T): Promise { + 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(filePath: string, paramPath: string): Promise { + try { + const jsonContent = await readJsonFile(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(filePath: string, key: string, value: any): Promise { + try { + const data = await readJsonFile(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; + } +} \ No newline at end of file diff --git a/src/utilities/logger.ts b/src/utilities/logger.ts index 7e5d55c..2c0cbb9 100644 --- a/src/utilities/logger.ts +++ b/src/utilities/logger.ts @@ -1,6 +1,6 @@ import pino from 'pino' import fs from 'fs' -import { getRootPath } from './files' +import { getRootPath } from './storage' // Array of log types const LOG_TYPES = ['http', 'game', 'gameMaster', 'app', 'queue', 'command'] as const diff --git a/src/utilities/files.ts b/src/utilities/storage.ts similarity index 100% rename from src/utilities/files.ts rename to src/utilities/storage.ts