1
0
forked from noxious/server

Improved readability of weather and date managers

This commit is contained in:
Dennis Postma 2025-01-01 22:09:44 +01:00
parent 0464538b1c
commit 2d6831b4ef
3 changed files with 113 additions and 101 deletions

View File

@ -1,75 +1,83 @@
import { Server } from 'socket.io' import { Server } from 'socket.io'
import Logger, { LoggerType } from '#application/logger' import Logger, { LoggerType } from '#application/logger'
import worldRepository from '#repositories/worldRepository' import worldRepository from '#repositories/worldRepository'
import worldService from '#services/worldService' import worldService from '#services/worldService'
import SocketManager from '#managers/socketManager'
class DateManager { class DateManager {
private static readonly GAME_SPEED = 8 // 24 game hours / 3 real hours private static readonly CONFIG = {
private static readonly UPDATE_INTERVAL = 1000 // 1 second GAME_SPEED: 8, // 24 game hours / 3 real hours
UPDATE_INTERVAL: 1000, // 1 second
} as const
private io: Server | null = null private io: Server | null = null
private intervalId: NodeJS.Timeout | null = null private intervalId: NodeJS.Timeout | null = null
private currentDate: Date = new Date() private currentDate = new Date()
private logger = Logger.type(LoggerType.APP) private readonly logger = Logger.type(LoggerType.APP)
public async boot(io: Server): Promise<void> { public async boot(): Promise<void> {
this.io = io this.io = SocketManager.getIO()
await this.loadDate() await this.loadDate()
this.startDateLoop() this.startDateLoop()
this.logger.info('Date manager loaded') this.logger.info('Date manager loaded')
} }
public async setTime(time: string): Promise<void> { public getCurrentDate(): Date {
try { return this.currentDate
let newDate: Date
// Check if it's just a time (HH:mm or HH:mm:ss format)
if (/^\d{1,2}:\d{2}(:\d{2})?$/.test(time)) {
const [hours, minutes] = time.split(':').map(Number)
newDate = new Date(this.currentDate) // Clone current date
newDate.setHours(hours, minutes)
} else {
// Treat as full datetime string
newDate = new Date(time)
if (isNaN(newDate.getTime())) return
} }
public async setTime(timeString: string): Promise<void> {
try {
const newDate = this.parseTimeString(timeString)
if (!newDate) return
this.currentDate = newDate this.currentDate = newDate
this.emitDate() this.emitDate()
await this.saveDate() await this.saveDate()
} catch (error) { } catch (error) {
this.logger.error(`Failed to set time: ${error instanceof Error ? error.message : String(error)}`) this.handleError('Failed to set time', error)
throw error throw error
} }
} }
public cleanup(): void {
this.intervalId && clearInterval(this.intervalId)
}
private async loadDate(): Promise<void> { private async loadDate(): Promise<void> {
try { try {
const world = await worldRepository.getFirst() const world = await worldRepository.getFirst()
this.currentDate = world?.date ?? new Date()
if (world) {
this.currentDate = world.date
}
} catch (error) { } catch (error) {
this.logger.error(`Failed to load date: ${error instanceof Error ? error.message : String(error)}`) this.handleError('Failed to load date', error)
this.currentDate = new Date() // Use current date as fallback this.currentDate = new Date()
} }
} }
private parseTimeString(timeString: string): Date | null {
const timeOnlyPattern = /^\d{1,2}:\d{2}(:\d{2})?$/
if (timeOnlyPattern.test(timeString)) {
const [hours, minutes] = timeString.split(':').map(Number)
const newDate = new Date(this.currentDate)
newDate.setHours(hours, minutes)
return newDate
}
const fullDate = new Date(timeString)
return isNaN(fullDate.getTime()) ? null : fullDate
}
private startDateLoop(): void { private startDateLoop(): void {
this.intervalId = setInterval(() => { this.intervalId = setInterval(() => {
this.advanceGameTime() this.advanceGameTime()
this.emitDate() this.emitDate()
void this.saveDate() void this.saveDate()
}, DateManager.UPDATE_INTERVAL) }, DateManager.CONFIG.UPDATE_INTERVAL)
} }
private advanceGameTime(): void { private advanceGameTime(): void {
if (!this.currentDate) { const advanceMilliseconds = DateManager.CONFIG.GAME_SPEED * DateManager.CONFIG.UPDATE_INTERVAL
this.currentDate = new Date()
}
const advanceMilliseconds = DateManager.GAME_SPEED * DateManager.UPDATE_INTERVAL
this.currentDate = new Date(this.currentDate.getTime() + advanceMilliseconds) this.currentDate = new Date(this.currentDate.getTime() + advanceMilliseconds)
} }
@ -79,22 +87,14 @@ class DateManager {
private async saveDate(): Promise<void> { private async saveDate(): Promise<void> {
try { try {
await worldService.update({ await worldService.update({ date: this.currentDate })
date: this.currentDate
})
} catch (error) { } catch (error) {
this.logger.error(`Failed to save date: ${error instanceof Error ? error.message : String(error)}`) this.handleError('Failed to save date', error)
} }
} }
public cleanup(): void { private handleError(message: string, error: unknown): void {
if (this.intervalId) { this.logger.error(`${message}: ${error instanceof Error ? error.message : String(error)}`)
clearInterval(this.intervalId)
}
}
public getCurrentDate(): Date {
return this.currentDate
} }
} }

View File

@ -1,10 +1,10 @@
import { Server } from 'socket.io' import { Server } from 'socket.io'
import Logger, { LoggerType } from '#application/logger' import Logger, { LoggerType } from '#application/logger'
import worldRepository from '#repositories/worldRepository' import worldRepository from '#repositories/worldRepository'
import worldService from '#services/worldService' import worldService from '#services/worldService'
import SocketManager from '#managers/socketManager'
interface WeatherState { type WeatherState = {
isRainEnabled: boolean isRainEnabled: boolean
rainPercentage: number rainPercentage: number
isFogEnabled: boolean isFogEnabled: boolean
@ -12,13 +12,18 @@ interface WeatherState {
} }
class WeatherManager { class WeatherManager {
private static readonly UPDATE_INTERVAL = 60000 // Check weather every minute private static readonly CONFIG = {
private static readonly RAIN_CHANCE = 0.2 // 20% chance of rain UPDATE_INTERVAL_MS: 60_000, // Check weather every minute
private static readonly FOG_CHANCE = 0.15 // 15% chance of fog RAIN_CHANCE: 0.2, // 20% chance
private readonly logger = Logger.type(LoggerType.APP) FOG_CHANCE: 0.15, // 15% chance
RAIN_PERCENTAGE_RANGE: { min: 50, max: 100 },
FOG_DENSITY_RANGE: { min: 30, max: 100 }
} as const
private readonly logger = Logger.type(LoggerType.APP)
private io: Server | null = null private io: Server | null = null
private intervalId: NodeJS.Timeout | null = null private intervalId: NodeJS.Timeout | null = null
private weatherState: WeatherState = { private weatherState: WeatherState = {
isRainEnabled: false, isRainEnabled: false,
rainPercentage: 0, rainPercentage: 0,
@ -26,37 +31,34 @@ class WeatherManager {
fogDensity: 0 fogDensity: 0
} }
public async boot(io: Server): Promise<void> { public async boot(): Promise<void> {
// this.io = io this.io = SocketManager.getIO()
// await this.loadWeather() await this.loadWeather()
// this.startWeatherLoop() this.startWeatherLoop()
this.logger.info('Weather manager loaded') this.logger.info('Weather manager loaded')
} }
public async toggleRain(): Promise<void> { public getWeatherState(): WeatherState {
this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled return { ...this.weatherState }
this.weatherState.rainPercentage = this.weatherState.isRainEnabled }
? Math.floor(Math.random() * 50) + 50 // 50-100%
: 0
await this.saveWeather() public async toggleRain(): Promise<void> {
this.emitWeather() this.updateWeatherProperty('rain')
await this.saveAndEmitWeather()
} }
public async toggleFog(): Promise<void> { public async toggleFog(): Promise<void> {
this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled this.updateWeatherProperty('fog')
this.weatherState.fogDensity = this.weatherState.isFogEnabled await this.saveAndEmitWeather()
? Math.floor((Math.random() * 0.7 + 0.3) * 100) // Convert 0.3-1.0 to 30-100 }
: 0
await this.saveWeather() public cleanup(): void {
this.emitWeather() this.intervalId && clearInterval(this.intervalId)
} }
private async loadWeather(): Promise<void> { private async loadWeather(): Promise<void> {
try { try {
const world = await worldRepository.getFirst() const world = await worldRepository.getFirst()
if (world) { if (world) {
this.weatherState = { this.weatherState = {
isRainEnabled: world.isRainEnabled, isRainEnabled: world.isRainEnabled,
@ -66,63 +68,73 @@ class WeatherManager {
} }
} }
} catch (error) { } catch (error) {
this.logger.error(`Failed to load weather: ${error instanceof Error ? error.message : String(error)}`) this.logError('load', error)
} }
} }
public getWeatherState(): WeatherState {
return this.weatherState
}
private startWeatherLoop(): void { private startWeatherLoop(): void {
this.intervalId = setInterval(async () => { this.intervalId = setInterval(async () => {
this.updateWeather() this.updateRandomWeather()
this.emitWeather() await this.saveAndEmitWeather()
await this.saveWeather().catch((error) => { }, WeatherManager.CONFIG.UPDATE_INTERVAL_MS)
this.logger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`)
})
}, WeatherManager.UPDATE_INTERVAL)
} }
private updateWeather(): void { private updateRandomWeather(): void {
// Update rain if (Math.random() < WeatherManager.CONFIG.RAIN_CHANCE) {
if (Math.random() < WeatherManager.RAIN_CHANCE) { this.updateWeatherProperty('rain')
}
if (Math.random() < WeatherManager.CONFIG.FOG_CHANCE) {
this.updateWeatherProperty('fog')
}
}
private updateWeatherProperty(type: 'rain' | 'fog'): void {
if (type === 'rain') {
this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled
this.weatherState.rainPercentage = this.weatherState.isRainEnabled this.weatherState.rainPercentage = this.weatherState.isRainEnabled
? Math.floor(Math.random() * 50) + 50 // 50-100% ? this.getRandomNumber(
WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.min,
WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.max
)
: 0 : 0
} }
// Update fog if (type === 'fog') {
if (Math.random() < WeatherManager.FOG_CHANCE) {
this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled
this.weatherState.fogDensity = this.weatherState.isFogEnabled this.weatherState.fogDensity = this.weatherState.isFogEnabled
? Math.floor((Math.random() * 0.7 + 0.3) * 100) // Convert 0.3-1.0 to 30-100 ? this.getRandomNumber(
WeatherManager.CONFIG.FOG_DENSITY_RANGE.min,
WeatherManager.CONFIG.FOG_DENSITY_RANGE.max
)
: 0 : 0
} }
} }
private async saveAndEmitWeather(): Promise<void> {
await this.saveWeather()
this.emitWeather()
}
private emitWeather(): void { private emitWeather(): void {
this.io?.emit('weather', this.weatherState) this.io?.emit('weather', this.weatherState)
} }
private async saveWeather(): Promise<void> { private async saveWeather(): Promise<void> {
try { try {
await worldService.update({ await worldService.update(this.weatherState)
isRainEnabled: this.weatherState.isRainEnabled,
rainPercentage: this.weatherState.rainPercentage,
isFogEnabled: this.weatherState.isFogEnabled,
fogDensity: this.weatherState.fogDensity
})
} catch (error) { } catch (error) {
this.logger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`) this.logError('save', error)
} }
} }
public cleanup(): void { private getRandomNumber(min: number, max: number): number {
if (this.intervalId) { return Math.floor(Math.random() * (max - min + 1)) + min
clearInterval(this.intervalId)
} }
private logError(operation: string, error: unknown): void {
this.logger.error(
`Failed to ${operation} weather: ${error instanceof Error ? error.message : String(error)}`
)
} }
} }

View File

@ -43,8 +43,8 @@ export class Server {
SocketManager.boot(this.app, this.http), SocketManager.boot(this.app, this.http),
QueueManager.boot(), QueueManager.boot(),
UserManager.boot(), UserManager.boot(),
// DateManager.boot(SocketManager.getIO()), DateManager.boot(),
// WeatherManager.boot(SocketManager.getIO()), WeatherManager.boot(),
ZoneManager.boot(), ZoneManager.boot(),
ConsoleManager.boot() ConsoleManager.boot()
]) ])