1
0
forked from noxious/server

Renamed files to storage, re-worked datetimeManager, added json help utilities

This commit is contained in:
Dennis Postma 2024-10-14 19:47:52 +02:00
parent 049b9de2b3
commit bb9f62a9c8
17 changed files with 86 additions and 47 deletions

View File

@ -1,7 +1,7 @@
{ {
"scripts": { "scripts": {
"start": "npx prisma migrate deploy && node dist/server.js", "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", "build": "tsc",
"format": "prettier --write src/" "format": "prettier --write src/"
}, },

View File

@ -2,7 +2,7 @@ import fs from 'fs'
import sharp from 'sharp' import sharp from 'sharp'
import { commandLogger } from '../utilities/logger' import { commandLogger } from '../utilities/logger'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import { getPublicPath } from '../utilities/files' import { getPublicPath } from '../utilities/storage'
import path from 'path' import path from 'path'
export default class TilesCommand { export default class TilesCommand {

View File

@ -3,7 +3,7 @@ import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import { commandLogger } from '../utilities/logger' import { commandLogger } from '../utilities/logger'
import { getAppPath } from '../utilities/files' import { getAppPath } from '../utilities/storage'
class CommandManager { class CommandManager {
private commands: Map<string, any> = new Map() private commands: Map<string, any> = new Map()

View File

@ -1,19 +1,21 @@
// src/managers/datetimeManager.ts // src/managers/datetimeManager.ts
import fs from 'fs/promises'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import { appLogger } from '../utilities/logger' 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 { class DatetimeManager {
private static readonly GAME_SPEED = 24 / 3 // 24 hours / 3 hours = 8x speed private static readonly GAME_SPEED = 8 // 24 game hours / 3 real hours
private static readonly UPDATE_INTERVAL = 1000 // Update every second for smooth second transitions private static readonly UPDATE_INTERVAL = 1000 // 1 second
private io: Server | null = null private io: Server | null = null
private intervalId: NodeJS.Timeout | null = null private intervalId: NodeJS.Timeout | null = null
private currentDateTime: Date = new Date()
public async boot(io: Server): Promise<void> { public async boot(io: Server): Promise<void> {
this.io = io this.io = io
await this.loadDateTime()
this.startDateTimeLoop() this.startDateTimeLoop()
appLogger.info('Datetime manager loaded') appLogger.info('Datetime manager loaded')
} }
@ -25,59 +27,47 @@ class DatetimeManager {
} }
} }
public async loadDateTime(): Promise<Date> { private async loadDateTime(): Promise<void> {
try { try {
const datetimeFilePath = this.getDatetimeFilePath() const datetimeString = await readJsonValue<string>(this.getWorldFilePath(), 'datetime')
const content = await fs.readFile(datetimeFilePath, 'utf-8') this.currentDateTime = new Date(datetimeString)
return new Date(content.trim())
} catch (error) { } catch (error) {
appLogger.error(`Failed to load datetime: ${error instanceof Error ? error.message : String(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 { private startDateTimeLoop(): void {
this.intervalId = setInterval(async () => { this.intervalId = setInterval(() => {
const currentDateTime = await this.loadDateTime() this.advanceGameTime()
this.advanceGameTime(currentDateTime) this.emitDateTime()
this.emitDateTime(currentDateTime) this.saveDateTime()
this.saveDateTimeIfNeeded(currentDateTime)
}, DatetimeManager.UPDATE_INTERVAL) }, DatetimeManager.UPDATE_INTERVAL)
} }
private advanceGameTime(currentDateTime: Date): void { private advanceGameTime(): void {
const advanceTime = (DatetimeManager.GAME_SPEED * DatetimeManager.UPDATE_INTERVAL) / 1000 * 1000 const advanceMilliseconds = DatetimeManager.GAME_SPEED * DatetimeManager.UPDATE_INTERVAL
currentDateTime.setTime(currentDateTime.getTime() + advanceTime) this.currentDateTime = new Date(this.currentDateTime.getTime() + advanceMilliseconds)
} }
private emitDateTime(currentDateTime: Date): void { private emitDateTime(): void {
this.io?.emit('datetime', this.formatDateTime(currentDateTime)) this.io?.emit('datetime', this.formatDateTime(this.currentDateTime))
} }
private formatDateTime(date: Date): string { private formatDateTime(date: Date): string {
return date.toISOString().slice(0, 19).replace('T', ' ') return date.toISOString().slice(0, 19).replace('T', ' ')
} }
private saveDateTimeIfNeeded(currentDateTime: Date): void { private async saveDateTime(): Promise<void> {
if (currentDateTime.getMilliseconds() < DatetimeManager.UPDATE_INTERVAL) {
this.saveDateTime(currentDateTime)
}
}
private async saveDateTime(currentDateTime: Date): Promise<void> {
try { try {
const datetimeFilePath = this.getDatetimeFilePath() await setJsonValue(this.getWorldFilePath(), 'datetime', this.formatDateTime(this.currentDateTime))
await fs.writeFile(datetimeFilePath, this.formatDateTime(currentDateTime))
} catch (error) { } catch (error) {
appLogger.error(`Failed to save datetime: ${error instanceof Error ? error.message : String(error)}`) appLogger.error(`Failed to save datetime: ${error instanceof Error ? error.message : String(error)}`)
} }
} }
private getDatetimeFilePath(): string { private getWorldFilePath(): string {
if (!doesPathExist(getRootPath('data'))) { return getRootPath('data', 'world.json')
createDir(getRootPath('data'))
}
return getRootPath('data', 'datetime.txt')
} }
} }

View File

@ -5,7 +5,7 @@ import { Server as SocketServer } from 'socket.io'
import { TSocket } from '../utilities/types' import { TSocket } from '../utilities/types'
import { queueLogger } from '../utilities/logger' import { queueLogger } from '../utilities/logger'
import fs from 'fs' import fs from 'fs'
import { getAppPath } from '../utilities/files' import { getAppPath } from '../utilities/storage'
class QueueManager { class QueueManager {
private connection!: IORedis private connection!: IORedis

View File

@ -1,7 +1,7 @@
import fs from 'fs' import fs from 'fs'
import express, { Application } from 'express' import express, { Application } from 'express'
import config from './utilities/config' 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 { createServer as httpServer, Server as HTTPServer } from 'http'
import { addHttpRoutes } from './utilities/http' import { addHttpRoutes } from './utilities/http'
import cors from 'cors' import cors from 'cors'

View File

@ -3,7 +3,7 @@ import { Server } from 'socket.io'
import { TSocket } from '../../../../utilities/types' import { TSocket } from '../../../../utilities/types'
import prisma from '../../../../utilities/prisma' import prisma from '../../../../utilities/prisma'
import characterRepository from '../../../../repositories/characterRepository' import characterRepository from '../../../../repositories/characterRepository'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
interface IPayload { interface IPayload {
object: string object: string

View File

@ -6,7 +6,7 @@ import prisma from '../../../../utilities/prisma'
import sharp from 'sharp' import sharp from 'sharp'
import characterRepository from '../../../../repositories/characterRepository' import characterRepository from '../../../../repositories/characterRepository'
import { gameMasterLogger } from '../../../../utilities/logger' import { gameMasterLogger } from '../../../../utilities/logger'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
interface IObjectData { interface IObjectData {
[key: string]: Buffer [key: string]: Buffer

View File

@ -3,7 +3,7 @@ import { TSocket } from '../../../../utilities/types'
import fs from 'fs/promises' import fs from 'fs/promises'
import prisma from '../../../../utilities/prisma' import prisma from '../../../../utilities/prisma'
import characterRepository from '../../../../repositories/characterRepository' import characterRepository from '../../../../repositories/characterRepository'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
export default class SpriteCreateEvent { export default class SpriteCreateEvent {
constructor( constructor(

View File

@ -4,7 +4,7 @@ import fs from 'fs'
import prisma from '../../../../utilities/prisma' import prisma from '../../../../utilities/prisma'
import CharacterManager from '../../../../managers/characterManager' import CharacterManager from '../../../../managers/characterManager'
import { gameMasterLogger } from '../../../../utilities/logger' import { gameMasterLogger } from '../../../../utilities/logger'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
type Payload = { type Payload = {
id: string id: string

View File

@ -5,7 +5,7 @@ import type { Prisma, SpriteAction } from '@prisma/client'
import { writeFile, mkdir } from 'node:fs/promises' import { writeFile, mkdir } from 'node:fs/promises'
import sharp from 'sharp' import sharp from 'sharp'
import CharacterManager from '../../../../managers/characterManager' import CharacterManager from '../../../../managers/characterManager'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & { type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & {
sprites: string[] sprites: string[]

View File

@ -4,7 +4,7 @@ import { TSocket } from '../../../../utilities/types'
import prisma from '../../../../utilities/prisma' import prisma from '../../../../utilities/prisma'
import characterRepository from '../../../../repositories/characterRepository' import characterRepository from '../../../../repositories/characterRepository'
import { gameMasterLogger } from '../../../../utilities/logger' import { gameMasterLogger } from '../../../../utilities/logger'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
type Payload = { type Payload = {
id: string id: string

View File

@ -5,7 +5,7 @@ import fs from 'fs/promises'
import prisma from '../../../../utilities/prisma' import prisma from '../../../../utilities/prisma'
import characterRepository from '../../../../repositories/characterRepository' import characterRepository from '../../../../repositories/characterRepository'
import { gameMasterLogger } from '../../../../utilities/logger' import { gameMasterLogger } from '../../../../utilities/logger'
import { getPublicPath } from '../../../../utilities/files' import { getPublicPath } from '../../../../utilities/storage'
interface ITileData { interface ITileData {
[key: string]: Buffer [key: string]: Buffer

View File

@ -12,7 +12,7 @@ import fs from 'fs'
import zoneRepository from '../repositories/zoneRepository' import zoneRepository from '../repositories/zoneRepository'
import zoneManager from '../managers/zoneManager' import zoneManager from '../managers/zoneManager'
import { httpLogger } from './logger' import { httpLogger } from './logger'
import { getPublicPath } from './files' import { getPublicPath } from './storage'
async function addHttpRoutes(app: Application) { async function addHttpRoutes(app: Application) {
/** /**

49
src/utilities/json.ts Normal file
View File

@ -0,0 +1,49 @@
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;
}
}

View File

@ -1,6 +1,6 @@
import pino from 'pino' import pino from 'pino'
import fs from 'fs' import fs from 'fs'
import { getRootPath } from './files' import { getRootPath } from './storage'
// Array of log types // Array of log types
const LOG_TYPES = ['http', 'game', 'gameMaster', 'app', 'queue', 'command'] as const const LOG_TYPES = ['http', 'game', 'gameMaster', 'app', 'queue', 'command'] as const