From ae0241fecba5fbc0e4f65102696a73ec9e33dba4 Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Tue, 5 Nov 2024 22:45:57 +0100 Subject: [PATCH] #184: Added weather manager that periodically changes the weather in-game and emits this to players --- src/managers/weatherManager.ts | 98 ++++++++++++++++++++++++++++++++++ src/server.ts | 4 ++ src/socketEvents/weather.ts | 24 +++++++++ 3 files changed, 126 insertions(+) create mode 100644 src/socketEvents/weather.ts diff --git a/src/managers/weatherManager.ts b/src/managers/weatherManager.ts index e69de29..4de93e4 100644 --- a/src/managers/weatherManager.ts +++ b/src/managers/weatherManager.ts @@ -0,0 +1,98 @@ +import { Server } from 'socket.io' +import { appLogger } from '../utilities/logger' +import { getRootPath } from '../utilities/storage' +import { readJsonValue, setJsonValue } from '../utilities/json' + +interface WeatherState { + isRainEnabled: boolean + rainPercentage: number + isFogEnabled: boolean + fogDensity: number +} + +class WeatherManager { + private static readonly UPDATE_INTERVAL = 60000 // Check weather every minute + private static readonly RAIN_CHANCE = 0.2 // 20% chance of rain + private static readonly FOG_CHANCE = 0.15 // 15% chance of fog + + private io: Server | null = null + private intervalId: NodeJS.Timeout | null = null + private weatherState: WeatherState = { + isRainEnabled: false, + rainPercentage: 0, + isFogEnabled: false, + fogDensity: 0 + } + + public async boot(io: Server): Promise { + this.io = io + await this.loadWeather() + this.startWeatherLoop() + appLogger.info('Weather manager loaded') + } + + private async loadWeather(): Promise { + try { + this.weatherState.isRainEnabled = await readJsonValue(this.getWorldFilePath(), 'isRainEnabled') + this.weatherState.rainPercentage = await readJsonValue(this.getWorldFilePath(), 'rainPercentage') + this.weatherState.isFogEnabled = await readJsonValue(this.getWorldFilePath(), 'isFogEnabled') + this.weatherState.fogDensity = await readJsonValue(this.getWorldFilePath(), 'fogDensity') + } catch (error) { + appLogger.error(`Failed to load weather: ${error instanceof Error ? error.message : String(error)}`) + } + } + + public async getWeatherState(): Promise { + return this.weatherState + } + + private startWeatherLoop(): void { + this.intervalId = setInterval(() => { + this.updateWeather() + this.emitWeather() + this.saveWeather() + }, WeatherManager.UPDATE_INTERVAL) + } + + private updateWeather(): void { + // Update rain + if (Math.random() < WeatherManager.RAIN_CHANCE) { + this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled + this.weatherState.rainPercentage = this.weatherState.isRainEnabled + ? Math.floor(Math.random() * 50) + 50 // 50-100% + : 0 + } + + // Update fog + 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 + : 0 + } + } + + private emitWeather(): void { + this.io?.emit('weather', this.weatherState) + } + + private async saveWeather(): Promise { + 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) + } catch (error) { + appLogger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`) + } + } + + private getWorldFilePath(): string { + return getRootPath('data', 'world.json') + } +} + +export default new WeatherManager() \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index bee71d0..6195f36 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,6 +16,7 @@ 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' export class Server { private readonly app: Application @@ -72,6 +73,9 @@ export class Server { // Load date manager await DateManager.boot(this.io) + // Load weather manager + await WeatherManager.boot(this.io) + // Load zoneEditor manager await ZoneManager.boot() diff --git a/src/socketEvents/weather.ts b/src/socketEvents/weather.ts new file mode 100644 index 0000000..444c7bc --- /dev/null +++ b/src/socketEvents/weather.ts @@ -0,0 +1,24 @@ +import { Server } from 'socket.io' +import { TSocket } from '../utilities/types' +import { gameLogger } from '../utilities/logger' +import WeatherManager from '../managers/weatherManager' + +export default class Weather { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('weather', this.handleEvent.bind(this)) + } + + private async handleEvent(): Promise { + try { + const weather = await WeatherManager.getWeatherState() + this.socket.emit('weather', weather) + } catch (error: any) { + gameLogger.error('error', error.message) + } + } +}