From 7330f6fb828b7982e763958c8f963453c5beb110 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 17 Oct 2024 00:57:33 +0100 Subject: [PATCH] First pass at improving weather simulation logic --- project/src/callbacks/WeatherCallbacks.ts | 1 + project/src/controllers/WeatherController.ts | 7 +- project/src/di/Container.ts | 4 + project/src/generators/WeatherGenerator.ts | 23 +++--- project/src/helpers/WeatherHelper.ts | 14 ++-- project/src/services/RaidWeatherService.ts | 86 ++++++++++++++++++++ project/src/utils/TimeUtil.ts | 22 +++++ 7 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 project/src/services/RaidWeatherService.ts diff --git a/project/src/callbacks/WeatherCallbacks.ts b/project/src/callbacks/WeatherCallbacks.ts index d3367989..c53dcc68 100644 --- a/project/src/callbacks/WeatherCallbacks.ts +++ b/project/src/callbacks/WeatherCallbacks.ts @@ -21,6 +21,7 @@ export class WeatherCallbacks { return this.httpResponse.getBody(this.weatherController.generate()); } + /** Handle client/localGame/weather */ public getLocalWeather( url: string, info: IEmptyRequestData, diff --git a/project/src/controllers/WeatherController.ts b/project/src/controllers/WeatherController.ts index b7ac7013..629e0e13 100644 --- a/project/src/controllers/WeatherController.ts +++ b/project/src/controllers/WeatherController.ts @@ -1,11 +1,12 @@ import { WeatherGenerator } from "@spt/generators/WeatherGenerator"; import { WeatherHelper } from "@spt/helpers/WeatherHelper"; -import { IWeatherData } from "@spt/models/eft/weather/IWeatherData"; +import { IWeather, IWeatherData } from "@spt/models/eft/weather/IWeatherData"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { IWeatherConfig } from "@spt/models/spt/config/IWeatherConfig"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { IGetLocalWeatherResponseData } from "@spt/models/spt/weather/IGetLocalWeatherResponseData"; import { ConfigServer } from "@spt/servers/ConfigServer"; +import { RaidWeatherService } from "@spt/services/RaidWeatherService"; import { SeasonalEventService } from "@spt/services/SeasonalEventService"; import { inject, injectable } from "tsyringe"; @@ -18,6 +19,7 @@ export class WeatherController { @inject("PrimaryLogger") protected logger: ILogger, @inject("ConfigServer") protected configServer: ConfigServer, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, + @inject("RaidWeatherService") protected raidWeatherService: RaidWeatherService, @inject("WeatherHelper") protected weatherHelper: WeatherHelper, ) { this.weatherConfig = this.configServer.getConfig(ConfigTypes.WEATHER); @@ -41,6 +43,7 @@ export class WeatherController { return this.weatherHelper.getInRaidTime(); } + /** Handle client/localGame/weather */ public generateLocal(sesssionId: string): IGetLocalWeatherResponseData { const result: IGetLocalWeatherResponseData = { season: @@ -50,7 +53,7 @@ export class WeatherController { weather: [], }; - result.weather.push(this.weatherGenerator.generateWeather()); + result.weather.push(...this.raidWeatherService.getUpcomingWeather()); return result; } diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index 2058c89b..e0b75e4f 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -235,6 +235,7 @@ import { RagfairPriceService } from "@spt/services/RagfairPriceService"; import { RagfairRequiredItemsService } from "@spt/services/RagfairRequiredItemsService"; import { RagfairTaxService } from "@spt/services/RagfairTaxService"; import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService"; +import { RaidWeatherService } from "@spt/services/RaidWeatherService"; import { RepairService } from "@spt/services/RepairService"; import { SeasonalEventService } from "@spt/services/SeasonalEventService"; import { TraderAssortService } from "@spt/services/TraderAssortService"; @@ -803,6 +804,9 @@ export class Container { depContainer.register("BotNameService", BotNameService, { lifecycle: Lifecycle.Singleton, }); + depContainer.register("RaidWeatherService", RaidWeatherService, { + lifecycle: Lifecycle.Singleton, + }); } private static registerServers(depContainer: DependencyContainer): void { diff --git a/project/src/generators/WeatherGenerator.ts b/project/src/generators/WeatherGenerator.ts index b29b906d..3cff7f91 100644 --- a/project/src/generators/WeatherGenerator.ts +++ b/project/src/generators/WeatherGenerator.ts @@ -79,7 +79,7 @@ export class WeatherGenerator { * Return randomised Weather data with help of config/weather.json * @returns Randomised weather data */ - public generateWeather(): IWeather { + public generateWeather(timestamp?: number): IWeather { const clouds = this.getWeightedClouds(); // Force rain to off if no clouds @@ -93,14 +93,14 @@ export class WeatherGenerator { rain: rain, rain_intensity: rain > 1 ? this.getRandomFloat("rainIntensity") : 0, fog: this.getWeightedFog(), - temp: this.getRandomFloat("temp"), + temp: this.getRandomFloat("temp"), // TODO - lower value at night / take into account season pressure: this.getRandomFloat("pressure"), time: "", date: "", - timestamp: 0, + timestamp: 0, // Added below }; - this.setCurrentDateTime(result); + this.setCurrentDateTime(result, timestamp); return result; } @@ -108,16 +108,17 @@ export class WeatherGenerator { /** * Set IWeather date/time/timestamp values to now * @param weather Object to update + * @param timestamp OPTIONAL, define timestamp used */ - protected setCurrentDateTime(weather: IWeather): void { - const currentDate = this.weatherHelper.getInRaidTime(); - const normalTime = this.getBSGFormattedTime(currentDate); - const formattedDate = this.timeUtil.formatDate(currentDate); - const datetime = `${formattedDate} ${normalTime}`; + protected setCurrentDateTime(weather: IWeather, timestamp?: number): void { + const inRaidTime = this.weatherHelper.getInRaidTime(timestamp); + const normalTime = this.getBSGFormattedTime(inRaidTime); + const formattedDate = this.timeUtil.formatDate(inRaidTime); + const datetimeBsgFormat = `${formattedDate} ${normalTime}`; - weather.timestamp = Math.floor(currentDate.getTime() / 1000); // matches weather.date + weather.timestamp = Math.floor(timestamp ? timestamp : inRaidTime.getTime() / 1000); // matches weather.date weather.date = formattedDate; // matches weather.timestamp - weather.time = datetime; // matches weather.timestamp + weather.time = datetimeBsgFormat; // matches weather.timestamp } protected getWeightedWindDirection(): WindDirection { diff --git a/project/src/helpers/WeatherHelper.ts b/project/src/helpers/WeatherHelper.ts index 5ac2a74d..902d6ce1 100644 --- a/project/src/helpers/WeatherHelper.ts +++ b/project/src/helpers/WeatherHelper.ts @@ -23,12 +23,16 @@ export class WeatherHelper { * @param currentDate (new Date()) * @returns Date object of current in-raid time */ - public getInRaidTime(): Date { + public getInRaidTime(timestamp?: number): Date { // tarkov time = (real time * 7 % 24 hr) + 3 hour - const russiaOffset = this.timeUtil.getHoursAsSeconds(3) * 1000; + const russiaOffsetMilliseconds = this.timeUtil.getHoursAsSeconds(2) * 1000; + const twentyFourHoursMilliseconds = this.timeUtil.getHoursAsSeconds(24) * 1000; + const currentTimestampMilliSeconds = timestamp ? timestamp : new Date().getTime(); + return new Date( - (russiaOffset + new Date().getTime() * this.weatherConfig.acceleration) % - (this.timeUtil.getHoursAsSeconds(24) * 1000), + ((russiaOffsetMilliseconds + currentTimestampMilliSeconds * this.weatherConfig.acceleration) % + twentyFourHoursMilliseconds) + + this.timeUtil.getStartOfDayTimestamp(timestamp), ); } @@ -40,7 +44,7 @@ export class WeatherHelper { public isNightTime(timeVariant: DateTime) { const time = this.getInRaidTime(); - // We get left side value, if player chose right side, set ahead 12 hrs + // getInRaidTime() provides left side value, if player chose right side, set ahead 12 hrs if (timeVariant === "PAST") { time.setHours(time.getHours() + 12); } diff --git a/project/src/services/RaidWeatherService.ts b/project/src/services/RaidWeatherService.ts new file mode 100644 index 00000000..569e2c25 --- /dev/null +++ b/project/src/services/RaidWeatherService.ts @@ -0,0 +1,86 @@ +import { WeatherGenerator } from "@spt/generators/WeatherGenerator"; +import { IWeather } from "@spt/models/eft/weather/IWeatherData"; +import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; +import { IWeatherConfig } from "@spt/models/spt/config/IWeatherConfig"; +import { ILogger } from "@spt/models/spt/utils/ILogger"; +import { ConfigServer } from "@spt/servers/ConfigServer"; +import { DatabaseService } from "@spt/services/DatabaseService"; +import { TimeUtil } from "@spt/utils/TimeUtil"; +import { inject, injectable } from "tsyringe"; + +@injectable() +export class RaidWeatherService { + protected weatherConfig: IWeatherConfig; + protected weatherForecast: IWeather[] = []; + + constructor( + @inject("PrimaryLogger") protected logger: ILogger, + @inject("DatabaseService") protected databaseService: DatabaseService, + @inject("TimeUtil") protected timeUtil: TimeUtil, + @inject("WeatherGenerator") protected weatherGenerator: WeatherGenerator, + @inject("ConfigServer") protected configServer: ConfigServer, + ) { + this.weatherConfig = this.configServer.getConfig(ConfigTypes.WEATHER); + + this.generateWeather(); + } + + public generateWeather() { + // When to start generating weather from + const staringTimestamp = this.getLastFullHourTimestamp(); + + // How far into future do we generate weather + const futureTimestampToReach = staringTimestamp + this.timeUtil.getHoursAsSeconds(24) * 1000; // TODO move 24 to config + // Keep adding new weather until we have reached desired future date + let nextTimestamp = staringTimestamp; + while (nextTimestamp <= futureTimestampToReach) { + const newWeather = this.weatherGenerator.generateWeather(nextTimestamp); + this.logger.warning(`Handling ${new Date(nextTimestamp)}`); + this.weatherForecast.push(newWeather); + nextTimestamp += 30 * 60 * 1000; // TODO move to config + } + } + + protected getLastFullHourTimestamp() { + const now = new Date(); + let hours = now.getHours(); + const minutes = now.getMinutes(); + + // If minutes are greater than 0, subtract 1 hour to get last full hour + if (minutes > 0) { + hours--; + } + + // Create a new Date object with the last full hour, 0 minutes, and 0 seconds + const lastFullHour = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, 0, 0); + + // Return timestamp of last full hour + return lastFullHour.getTime(); + } + + public getCurrentWeather(): IWeather { + // Clear expired weather data + this.weatherForecast = this.weatherForecast.filter((x) => x.timestamp < this.timeUtil.getTimestamp()); + + // return first weather object that is greater than/equal to now + const result = this.weatherForecast.find((x) => x.timestamp >= this.timeUtil.getTimestamp()); + if (!result) { + this.generateWeather(); + } + + return this.weatherForecast.find((x) => x.timestamp >= this.timeUtil.getTimestamp()); + } + + public getUpcomingWeather(): IWeather[] { + // Clear expired weather data + this.weatherForecast = this.weatherForecast.filter((x) => x.timestamp < this.timeUtil.getTimestamp()); + + // return first weather object that is greater than/equal to now + const result = this.weatherForecast.filter((x) => x.timestamp >= this.timeUtil.getTimestamp()); + if (result.length === 0) { + this.generateWeather(); + } + + return this.weatherForecast.filter((x) => x.timestamp >= this.timeUtil.getTimestamp()); + } +} diff --git a/project/src/utils/TimeUtil.ts b/project/src/utils/TimeUtil.ts index 7982f95b..2dfe73ee 100644 --- a/project/src/utils/TimeUtil.ts +++ b/project/src/utils/TimeUtil.ts @@ -71,6 +71,17 @@ export class TimeUtil { return Math.floor(new Date().getTime() / 1000); } + public getStartOfDayTimestamp(timestamp?: number): number { + const now = timestamp ? new Date(timestamp) : new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + const day = now.getDate(); + + const todayTimestamp = new Date(year, month, day, 0, 0, 0); + + return todayTimestamp.getTime(); + } + /** * Get timestamp of today + passed in day count * @param daysFromNow Days from now @@ -82,6 +93,17 @@ export class TimeUtil { return currentTimeStamp + desiredDaysAsSeconds; } + /** + * Get timestamp of today + passed in hour count + * @param daysFromNow Days from now + */ + public getTimeStampFromNowHours(hoursFromNow: number): number { + const currentTimeStamp = this.getTimestamp(); + const desiredHoursAsSeconds = this.getHoursAsSeconds(hoursFromNow); + + return currentTimeStamp + desiredHoursAsSeconds; + } + /** * Gets the current time in UTC in a format suitable for mail in EFT. *