forked from noxious/server
Improved readability of weather and date managers
This commit is contained in:
parent
0464538b1c
commit
2d6831b4ef
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
])
|
])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user