diff --git a/project/assets/configs/weather.json b/project/assets/configs/weather.json index 3f47f441..c9598672 100644 --- a/project/assets/configs/weather.json +++ b/project/assets/configs/weather.json @@ -1,6 +1,7 @@ { "acceleration": 7, "weather": { + "generateWeatherAmountHours": 24, "clouds": { "values": [-1.5, -1, 0, 0.5, 1, 1.5], "weights": [60, 50, 15, 5, 4, 3] @@ -36,7 +37,11 @@ "pressure": { "min": 760, "max": 764 - } + }, + "timePeriod": { + "values": [15, 30], + "weights": [1, 2] + } }, "seasonDates": [ { diff --git a/project/src/models/spt/config/IWeatherConfig.ts b/project/src/models/spt/config/IWeatherConfig.ts index 2a4513e2..c427c0aa 100644 --- a/project/src/models/spt/config/IWeatherConfig.ts +++ b/project/src/models/spt/config/IWeatherConfig.ts @@ -6,7 +6,7 @@ import { IBaseConfig } from "@spt/models/spt/config/IBaseConfig"; export interface IWeatherConfig extends IBaseConfig { kind: "spt-weather"; acceleration: number; - weather: Weather; + weather: IWeatherValues; seasonDates: ISeasonDateTimes[]; overrideSeason?: Season; } @@ -20,7 +20,9 @@ export interface ISeasonDateTimes { endMonth: number; } -export interface Weather { +export interface IWeatherValues { + /** How many hours to generate weather data into the future */ + generateWeatherAmountHours: number; clouds: WeatherSettings; windSpeed: WeatherSettings; windDirection: WeatherSettings; @@ -30,6 +32,8 @@ export interface Weather { fog: WeatherSettings; temp: MinMax; pressure: MinMax; + /** Length of each weather period */ + timePeriod: WeatherSettings; } export interface WeatherSettings { diff --git a/project/src/services/RaidWeatherService.ts b/project/src/services/RaidWeatherService.ts index 569e2c25..54f1acd7 100644 --- a/project/src/services/RaidWeatherService.ts +++ b/project/src/services/RaidWeatherService.ts @@ -1,4 +1,5 @@ import { WeatherGenerator } from "@spt/generators/WeatherGenerator"; +import { WeightedRandomHelper } from "@spt/helpers/WeightedRandomHelper"; import { IWeather } from "@spt/models/eft/weather/IWeatherData"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { IWeatherConfig } from "@spt/models/spt/config/IWeatherConfig"; @@ -18,6 +19,7 @@ export class RaidWeatherService { @inject("DatabaseService") protected databaseService: DatabaseService, @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("WeatherGenerator") protected weatherGenerator: WeatherGenerator, + @inject("WeightedRandomHelper") protected weightedRandomHelper: WeightedRandomHelper, @inject("ConfigServer") protected configServer: ConfigServer, ) { this.weatherConfig = this.configServer.getConfig(ConfigTypes.WEATHER); @@ -25,62 +27,67 @@ export class RaidWeatherService { this.generateWeather(); } + /** + * Generate 24 hours of weather data starting from midnight today + */ public generateWeather() { - // When to start generating weather from - const staringTimestamp = this.getLastFullHourTimestamp(); + // When to start generating weather from in milliseconds + const staringTimestampMs = this.timeUtil.getTodaysMidnightTimestamp(); // How far into future do we generate weather - const futureTimestampToReach = staringTimestamp + this.timeUtil.getHoursAsSeconds(24) * 1000; // TODO move 24 to config + const futureTimestampToReachMs = + staringTimestampMs + + this.timeUtil.getHoursAsSeconds(this.weatherConfig.weather.generateWeatherAmountHours) * 1000; // Convert to milliseconds + // 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)}`); + let nextTimestampMs = staringTimestampMs; + while (nextTimestampMs <= futureTimestampToReachMs) { + const newWeather = this.weatherGenerator.generateWeather(nextTimestampMs); + this.logger.warning(`Handling ${new Date(nextTimestampMs)}`); this.weatherForecast.push(newWeather); - nextTimestamp += 30 * 60 * 1000; // TODO move to config + nextTimestampMs += this.getWeightedWeatherTimePeriodMs(); } } - protected getLastFullHourTimestamp() { - const now = new Date(); - let hours = now.getHours(); - const minutes = now.getMinutes(); + protected getWeightedWeatherTimePeriodMs(): number { + const chosenTimePeriodMinutes = this.weightedRandomHelper.weightedRandom( + this.weatherConfig.weather.timePeriod.values, + this.weatherConfig.weather.timePeriod.weights, + ).item; - // 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(); + return chosenTimePeriodMinutes * 60 * 1000; // Convert to milliseconds } + /** + * Find the first matching weather object that applies to the current time + */ 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(); - } + this.validateWeatherDataExists(); return this.weatherForecast.find((x) => x.timestamp >= this.timeUtil.getTimestamp()); } + /** + * Find the first matching weather object that applies to the current time + all following weather data generated + */ 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(); - } + this.validateWeatherDataExists(); return this.weatherForecast.filter((x) => x.timestamp >= this.timeUtil.getTimestamp()); } + + /** + * Ensure future weather data exists + */ + protected validateWeatherDataExists() { + // Clear expired weather data + this.weatherForecast = this.weatherForecast.filter((x) => x.timestamp < this.timeUtil.getTimestamp()); + + // Check data exists for current time + const result = this.weatherForecast.filter((x) => x.timestamp >= this.timeUtil.getTimestamp()); + if (result.length === 0) { + // TODO - replace with better check, if < 1 hours worth of data exists? + this.generateWeather(); + } + } } diff --git a/project/src/utils/TimeUtil.ts b/project/src/utils/TimeUtil.ts index 2dfe73ee..acdafb68 100644 --- a/project/src/utils/TimeUtil.ts +++ b/project/src/utils/TimeUtil.ts @@ -138,4 +138,26 @@ export class TimeUtil { (60 - now.getMinutes()) * 60 * 1000 - now.getSeconds() * 1000 - now.getMilliseconds(); return (now.getTime() + millisecondsUntilNextHour) / 1000; } + + /** + * Returns the current days timestamp at 00:00 + * e.g. current time: 13th march 14:22 will return 13th march 00:00 + * @returns Timestamp + */ + public getTodaysMidnightTimestamp(): number { + const now = new Date(); + let hours = now.getHours(); + const minutes = now.getMinutes(); + + // If minutes 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 above as timestamp + return lastFullHour.getTime(); + } }