First pass at improving weather simulation logic

This commit is contained in:
Dev 2024-10-17 00:57:33 +01:00
parent c7904a3a41
commit 7330f6fb82
7 changed files with 139 additions and 18 deletions

View File

@ -21,6 +21,7 @@ export class WeatherCallbacks {
return this.httpResponse.getBody(this.weatherController.generate());
}
/** Handle client/localGame/weather */
public getLocalWeather(
url: string,
info: IEmptyRequestData,

View File

@ -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;
}

View File

@ -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", BotNameService, {
lifecycle: Lifecycle.Singleton,
});
depContainer.register<RaidWeatherService>("RaidWeatherService", RaidWeatherService, {
lifecycle: Lifecycle.Singleton,
});
}
private static registerServers(depContainer: DependencyContainer): void {

View File

@ -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 {

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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.
*