Moved code from gameStart()
into server start via new class PostDbLoadService
Fixed player adding their name multiple times to PMCs inside `addPlayerToPMCNames()` Updated `enableSeasonalEvents()` to not require a session id, moved player-specific code into new function `givePlayerSeasonalGifts()`
This commit is contained in:
parent
0c153fc242
commit
9eba62d5e2
@ -6,7 +6,6 @@ import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
|||||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
|
import { PreSptModLoader } from "@spt/loaders/PreSptModLoader";
|
||||||
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
import { IEmptyRequestData } from "@spt/models/eft/common/IEmptyRequestData";
|
||||||
import { ILocation } from "@spt/models/eft/common/ILocation";
|
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
import { IBodyPartHealth } from "@spt/models/eft/common/tables/IBotBase";
|
import { IBodyPartHealth } from "@spt/models/eft/common/tables/IBotBase";
|
||||||
import { ICheckVersionResponse } from "@spt/models/eft/game/ICheckVersionResponse";
|
import { ICheckVersionResponse } from "@spt/models/eft/game/ICheckVersionResponse";
|
||||||
@ -23,15 +22,10 @@ import { BonusType } from "@spt/models/enums/BonusType";
|
|||||||
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
import { HideoutAreas } from "@spt/models/enums/HideoutAreas";
|
import { HideoutAreas } from "@spt/models/enums/HideoutAreas";
|
||||||
import { SkillTypes } from "@spt/models/enums/SkillTypes";
|
import { SkillTypes } from "@spt/models/enums/SkillTypes";
|
||||||
import { Traders } from "@spt/models/enums/Traders";
|
|
||||||
import { Weapons } from "@spt/models/enums/Weapons";
|
|
||||||
import { IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
import { IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
||||||
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
|
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
|
||||||
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
||||||
import { IHttpConfig } from "@spt/models/spt/config/IHttpConfig";
|
import { IHttpConfig } from "@spt/models/spt/config/IHttpConfig";
|
||||||
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig";
|
|
||||||
import { ILootConfig } from "@spt/models/spt/config/ILootConfig";
|
|
||||||
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
|
|
||||||
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
|
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
|
||||||
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { ConfigServer } from "@spt/servers/ConfigServer";
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
@ -41,6 +35,7 @@ import { GiftService } from "@spt/services/GiftService";
|
|||||||
import { ItemBaseClassService } from "@spt/services/ItemBaseClassService";
|
import { ItemBaseClassService } from "@spt/services/ItemBaseClassService";
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
import { OpenZoneService } from "@spt/services/OpenZoneService";
|
import { OpenZoneService } from "@spt/services/OpenZoneService";
|
||||||
|
import { PostDbLoadService } from "@spt/services/PostDbLoadService";
|
||||||
import { ProfileActivityService } from "@spt/services/ProfileActivityService";
|
import { ProfileActivityService } from "@spt/services/ProfileActivityService";
|
||||||
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
||||||
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService";
|
import { RaidTimeAdjustmentService } from "@spt/services/RaidTimeAdjustmentService";
|
||||||
@ -55,11 +50,8 @@ import { inject, injectable } from "tsyringe";
|
|||||||
export class GameController {
|
export class GameController {
|
||||||
protected httpConfig: IHttpConfig;
|
protected httpConfig: IHttpConfig;
|
||||||
protected coreConfig: ICoreConfig;
|
protected coreConfig: ICoreConfig;
|
||||||
protected locationConfig: ILocationConfig;
|
|
||||||
protected ragfairConfig: IRagfairConfig;
|
protected ragfairConfig: IRagfairConfig;
|
||||||
protected hideoutConfig: IHideoutConfig;
|
protected hideoutConfig: IHideoutConfig;
|
||||||
protected pmcConfig: IPmcConfig;
|
|
||||||
protected lootConfig: ILootConfig;
|
|
||||||
protected botConfig: IBotConfig;
|
protected botConfig: IBotConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -75,6 +67,7 @@ export class GameController {
|
|||||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
|
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("PostDbLoadService") protected postDbLoadService: PostDbLoadService,
|
||||||
@inject("CustomLocationWaveService") protected customLocationWaveService: CustomLocationWaveService,
|
@inject("CustomLocationWaveService") protected customLocationWaveService: CustomLocationWaveService,
|
||||||
@inject("OpenZoneService") protected openZoneService: OpenZoneService,
|
@inject("OpenZoneService") protected openZoneService: OpenZoneService,
|
||||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||||
@ -88,20 +81,13 @@ export class GameController {
|
|||||||
) {
|
) {
|
||||||
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP);
|
||||||
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
||||||
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
|
||||||
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||||
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||||
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
|
||||||
this.lootConfig = this.configServer.getConfig(ConfigTypes.LOOT);
|
|
||||||
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public load(): void {
|
public load(): void {
|
||||||
// Regenerate base cache now mods are loaded and game is starting
|
this.postDbLoadService.performPostDbLoadActions();
|
||||||
// Mods that add items and use the baseClass service generate the cache including their items, the next mod that
|
|
||||||
// add items gets left out,causing warnings
|
|
||||||
this.itemBaseClassService.hydrateItemBaseClassCache();
|
|
||||||
this.addCustomLooseLootPositions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -113,28 +99,6 @@ export class GameController {
|
|||||||
|
|
||||||
this.profileActivityService.setActivityTimestamp(sessionID);
|
this.profileActivityService.setActivityTimestamp(sessionID);
|
||||||
|
|
||||||
if (this.coreConfig.fixes.fixShotgunDispersion) {
|
|
||||||
this.fixShotgunDispersions();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.locationConfig.addOpenZonesToAllMaps) {
|
|
||||||
this.openZoneService.applyZoneChangesToAllMaps();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.locationConfig.addCustomBotWavesToMaps) {
|
|
||||||
this.customLocationWaveService.applyWaveChangesToAllMaps();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.locationConfig.enableBotTypeLimits) {
|
|
||||||
this.adjustMapBotLimits();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.adjustLooseLootSpawnProbabilities();
|
|
||||||
|
|
||||||
this.checkTraderRepairValuesExist();
|
|
||||||
|
|
||||||
this.adjustLocationBotValues();
|
|
||||||
|
|
||||||
// repeatableQuests are stored by in profile.Quests due to the responses of the client (e.g. Quests in
|
// repeatableQuests are stored by in profile.Quests due to the responses of the client (e.g. Quests in
|
||||||
// offraidData). Since we don't want to clutter the Quests list, we need to remove all completed (failed or
|
// offraidData). Since we don't want to clutter the Quests list, we need to remove all completed (failed or
|
||||||
// successful) repeatable quests. We also have to remove the Counters from the repeatableQuests
|
// successful) repeatable quests. We also have to remove the Counters from the repeatableQuests
|
||||||
@ -182,18 +146,6 @@ export class GameController {
|
|||||||
this.updateProfileHealthValues(pmcProfile);
|
this.updateProfileHealthValues(pmcProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.locationConfig.fixEmptyBotWavesSettings.enabled) {
|
|
||||||
this.fixBrokenOfflineMapWaves();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.locationConfig.rogueLighthouseSpawnTimeSettings.enabled) {
|
|
||||||
this.fixRoguesSpawningInstantlyOnLighthouse();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.enabled) {
|
|
||||||
this.splitBotWavesIntoSingleWaves();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pmcProfile.Inventory) {
|
if (pmcProfile.Inventory) {
|
||||||
this.sendPraporGiftsToNewProfiles(pmcProfile);
|
this.sendPraporGiftsToNewProfiles(pmcProfile);
|
||||||
|
|
||||||
@ -210,35 +162,19 @@ export class GameController {
|
|||||||
|
|
||||||
this.logProfileDetails(fullProfile);
|
this.logProfileDetails(fullProfile);
|
||||||
|
|
||||||
this.adjustLabsRaiderSpawnRate();
|
|
||||||
|
|
||||||
this.adjustHideoutCraftTimes(this.hideoutConfig.overrideCraftTimeSeconds);
|
|
||||||
this.adjustHideoutBuildTimes(this.hideoutConfig.overrideBuildTimeSeconds);
|
|
||||||
|
|
||||||
this.removePraporTestMessage();
|
|
||||||
|
|
||||||
this.saveActiveModsToProfile(fullProfile);
|
this.saveActiveModsToProfile(fullProfile);
|
||||||
|
|
||||||
this.validateQuestAssortUnlocksExist();
|
|
||||||
|
|
||||||
if (pmcProfile.Info) {
|
if (pmcProfile.Info) {
|
||||||
this.addPlayerToPMCNames(pmcProfile);
|
this.addPlayerToPMCNames(pmcProfile);
|
||||||
|
|
||||||
this.checkForAndRemoveUndefinedDialogs(fullProfile);
|
this.checkForAndRemoveUndefinedDialogs(fullProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.seasonalEventService.isAutomaticEventDetectionEnabled()) {
|
|
||||||
this.seasonalEventService.enableSeasonalEvents(sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pmcProfile?.Skills?.Common) {
|
if (pmcProfile?.Skills?.Common) {
|
||||||
this.warnOnActiveBotReloadSkill(pmcProfile);
|
this.warnOnActiveBotReloadSkill(pmcProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flea bsg blacklist is off
|
this.seasonalEventService.givePlayerSeasonalGifts(sessionID);
|
||||||
if (!this.ragfairConfig.dynamic.blacklist.enableBsgList) {
|
|
||||||
this.setAllDbItemsAsSellableOnFlea();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,189 +239,6 @@ export class GameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected adjustHideoutCraftTimes(overrideSeconds: number): void {
|
|
||||||
if (overrideSeconds === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const craft of this.databaseService.getHideout().production.recipes) {
|
|
||||||
// Only adjust crafts ABOVE the override
|
|
||||||
craft.productionTime = Math.min(craft.productionTime, overrideSeconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjust all hideout craft times to be no higher than the override
|
|
||||||
*/
|
|
||||||
protected adjustHideoutBuildTimes(overrideSeconds: number): void {
|
|
||||||
if (overrideSeconds === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const area of this.databaseService.getHideout().areas) {
|
|
||||||
for (const stage of Object.values(area.stages)) {
|
|
||||||
// Only adjust crafts ABOVE the override
|
|
||||||
stage.constructionTime = Math.min(stage.constructionTime, overrideSeconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected adjustLocationBotValues(): void {
|
|
||||||
const mapsDb = this.databaseService.getLocations();
|
|
||||||
|
|
||||||
for (const locationKey in this.botConfig.maxBotCap) {
|
|
||||||
const map: ILocation = mapsDb[locationKey];
|
|
||||||
if (!map) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
map.base.BotMaxPvE = this.botConfig.maxBotCap[locationKey];
|
|
||||||
|
|
||||||
// make values no larger than 30 secs
|
|
||||||
map.base.BotStart = Math.min(map.base.BotStart, 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Out of date/incorrectly made trader mods forget this data
|
|
||||||
*/
|
|
||||||
protected checkTraderRepairValuesExist(): void {
|
|
||||||
const traders = this.databaseService.getTraders();
|
|
||||||
for (const trader of Object.values(traders)) {
|
|
||||||
if (!trader?.base?.repair) {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("trader-missing_repair_property_using_default", {
|
|
||||||
traderId: trader.base._id,
|
|
||||||
nickname: trader.base.nickname,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// use ragfair trader as a default
|
|
||||||
trader.base.repair = this.cloner.clone(traders.ragfair.base.repair);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trader.base.repair?.quality === undefined) {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("trader-missing_repair_quality_property_using_default", {
|
|
||||||
traderId: trader.base._id,
|
|
||||||
nickname: trader.base.nickname,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// use ragfair trader as a default
|
|
||||||
trader.base.repair.quality = this.cloner.clone(traders.ragfair.base.repair.quality);
|
|
||||||
trader.base.repair.quality = traders.ragfair.base.repair.quality;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addCustomLooseLootPositions(): void {
|
|
||||||
const looseLootPositionsToAdd = this.lootConfig.looseLoot;
|
|
||||||
for (const [mapId, positionsToAdd] of Object.entries(looseLootPositionsToAdd)) {
|
|
||||||
if (!mapId) {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("location-unable_to_add_custom_loot_position", mapId),
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapLooseLoot = this.databaseService.getLocation(mapId).looseLoot;
|
|
||||||
if (!mapLooseLoot) {
|
|
||||||
this.logger.warning(this.localisationService.getText("location-map_has_no_loose_loot_data", mapId));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const positionToAdd of positionsToAdd) {
|
|
||||||
// Exists already, add new items to existing positions pool
|
|
||||||
const existingLootPosition = mapLooseLoot.spawnpoints.find(
|
|
||||||
(x) => x.template.Id === positionToAdd.template.Id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingLootPosition) {
|
|
||||||
existingLootPosition.template.Items.push(...positionToAdd.template.Items);
|
|
||||||
existingLootPosition.itemDistribution.push(...positionToAdd.itemDistribution);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// New position, add entire object
|
|
||||||
mapLooseLoot.spawnpoints.push(positionToAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected adjustLooseLootSpawnProbabilities(): void {
|
|
||||||
if (!this.lootConfig.looseLootSpawnPointAdjustments) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [mapId, mapAdjustments] of Object.entries(this.lootConfig.looseLootSpawnPointAdjustments)) {
|
|
||||||
const mapLooseLootData = this.databaseService.getLocation(mapId).looseLoot;
|
|
||||||
if (!mapLooseLootData) {
|
|
||||||
this.logger.warning(this.localisationService.getText("location-map_has_no_loose_loot_data", mapId));
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [lootKey, newChanceValue] of Object.entries(mapAdjustments)) {
|
|
||||||
const lootPostionToAdjust = mapLooseLootData.spawnpoints.find(
|
|
||||||
(spawnPoint) => spawnPoint.template.Id === lootKey,
|
|
||||||
);
|
|
||||||
if (!lootPostionToAdjust) {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("location-unable_to_adjust_loot_position_on_map", {
|
|
||||||
lootKey: lootKey,
|
|
||||||
mapId: mapId,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lootPostionToAdjust.probability = newChanceValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Apply custom limits on bot types as defined in configs/location.json/botTypeLimits */
|
|
||||||
protected adjustMapBotLimits(): void {
|
|
||||||
const mapsDb = this.databaseService.getLocations();
|
|
||||||
if (!this.locationConfig.botTypeLimits) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const mapId in this.locationConfig.botTypeLimits) {
|
|
||||||
const map: ILocation = mapsDb[mapId];
|
|
||||||
if (!map) {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("bot-unable_to_edit_limits_of_unknown_map", mapId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const botToLimit of this.locationConfig.botTypeLimits[mapId]) {
|
|
||||||
const index = map.base.MinMaxBots.findIndex((x) => x.WildSpawnType === botToLimit.type);
|
|
||||||
if (index !== -1) {
|
|
||||||
// Existing bot type found in MinMaxBots array, edit
|
|
||||||
const limitObjectToUpdate = map.base.MinMaxBots[index];
|
|
||||||
limitObjectToUpdate.min = botToLimit.min;
|
|
||||||
limitObjectToUpdate.max = botToLimit.max;
|
|
||||||
} else {
|
|
||||||
// Bot type not found, add new object
|
|
||||||
map.base.MinMaxBots.push({
|
|
||||||
// Bot type not found, add new object
|
|
||||||
WildSpawnType: botToLimit.type,
|
|
||||||
min: botToLimit.min,
|
|
||||||
max: botToLimit.max,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle client/game/config
|
* Handle client/game/config
|
||||||
*/
|
*/
|
||||||
@ -568,20 +321,6 @@ export class GameController {
|
|||||||
return this.raidTimeAdjustmentService.getRaidAdjustments(sessionId, request);
|
return this.raidTimeAdjustmentService.getRaidAdjustments(sessionId, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* BSG have two values for shotgun dispersion, we make sure both have the same value
|
|
||||||
*/
|
|
||||||
protected fixShotgunDispersions(): void {
|
|
||||||
const itemDb = this.databaseService.getItems();
|
|
||||||
|
|
||||||
const shotguns = [Weapons.SHOTGUN_12G_SAIGA_12K, Weapons.SHOTGUN_20G_TOZ_106, Weapons.SHOTGUN_12G_M870];
|
|
||||||
for (const shotgunId of shotguns) {
|
|
||||||
if (itemDb[shotgunId]._props.ShotgunDispersion) {
|
|
||||||
itemDb[shotgunId]._props.shotgunDispersion = itemDb[shotgunId]._props.ShotgunDispersion;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Players set botReload to a high value and don't expect the crazy fast reload speeds, give them a warn about it
|
* Players set botReload to a high value and don't expect the crazy fast reload speeds, give them a warn about it
|
||||||
* @param pmcProfile Player profile
|
* @param pmcProfile Player profile
|
||||||
@ -593,19 +332,6 @@ export class GameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected setAllDbItemsAsSellableOnFlea(): void {
|
|
||||||
const dbItems = Object.values(this.databaseService.getItems());
|
|
||||||
for (const item of dbItems) {
|
|
||||||
if (
|
|
||||||
item._type === "Item" &&
|
|
||||||
!item._props?.CanSellOnRagfair &&
|
|
||||||
!this.ragfairConfig.dynamic.blacklist.custom.includes(item._id)
|
|
||||||
) {
|
|
||||||
item._props.CanSellOnRagfair = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When player logs in, iterate over all active effects and reduce timer
|
* When player logs in, iterate over all active effects and reduce timer
|
||||||
* @param pmcProfile Profile to adjust values for
|
* @param pmcProfile Profile to adjust values for
|
||||||
@ -691,56 +417,6 @@ export class GameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Waves with an identical min/max values spawn nothing, the number of bots that spawn is the difference between min and max
|
|
||||||
*/
|
|
||||||
protected fixBrokenOfflineMapWaves(): void {
|
|
||||||
const locations = this.databaseService.getLocations();
|
|
||||||
for (const locationKey in locations) {
|
|
||||||
// Skip ignored maps
|
|
||||||
if (this.locationConfig.fixEmptyBotWavesSettings.ignoreMaps.includes(locationKey)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop over all of the locations waves and look for waves with identical min and max slots
|
|
||||||
const location: ILocation = locations[locationKey];
|
|
||||||
if (!location.base) {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("location-unable_to_fix_broken_waves_missing_base", locationKey),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const wave of location.base.waves ?? []) {
|
|
||||||
if (wave.slots_max - wave.slots_min === 0) {
|
|
||||||
this.logger.debug(
|
|
||||||
`Fixed ${wave.WildSpawnType} Spawn: ${locationKey} wave: ${wave.number} of type: ${wave.WildSpawnType} in zone: ${wave.SpawnPoints} with Max Slots of ${wave.slots_max}`,
|
|
||||||
);
|
|
||||||
wave.slots_max++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make Rogues spawn later to allow for scavs to spawn first instead of rogues filling up all spawn positions
|
|
||||||
*/
|
|
||||||
protected fixRoguesSpawningInstantlyOnLighthouse(): void {
|
|
||||||
const rogueSpawnDelaySeconds = this.locationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds;
|
|
||||||
const lighthouse = this.databaseService.getLocations().lighthouse?.base;
|
|
||||||
if (!lighthouse) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find Rogues that spawn instantly
|
|
||||||
const instantRogueBossSpawns = lighthouse.BossLocationSpawn.filter(
|
|
||||||
(spawn) => spawn.BossName === "exUsec" && spawn.Time === -1,
|
|
||||||
);
|
|
||||||
for (const wave of instantRogueBossSpawns) {
|
|
||||||
wave.Time = rogueSpawnDelaySeconds;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send starting gifts to profile after x days
|
* Send starting gifts to profile after x days
|
||||||
* @param pmcProfile Profile to add gifts to
|
* @param pmcProfile Profile to add gifts to
|
||||||
@ -761,71 +437,6 @@ export class GameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find and split waves with large numbers of bots into smaller waves - BSG appears to reduce the size of these
|
|
||||||
* waves to one bot when they're waiting to spawn for too long
|
|
||||||
*/
|
|
||||||
protected splitBotWavesIntoSingleWaves(): void {
|
|
||||||
const locations = this.databaseService.getLocations();
|
|
||||||
for (const locationKey in locations) {
|
|
||||||
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.ignoreMaps.includes(locationKey)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all maps
|
|
||||||
const location: ILocation = locations[locationKey];
|
|
||||||
for (const wave of location.base.waves) {
|
|
||||||
// Wave has size that makes it candidate for splitting
|
|
||||||
if (
|
|
||||||
wave.slots_max - wave.slots_min >=
|
|
||||||
this.locationConfig.splitWaveIntoSingleSpawnsSettings.waveSizeThreshold
|
|
||||||
) {
|
|
||||||
// Get count of bots to be spawned in wave
|
|
||||||
const waveSize = wave.slots_max - wave.slots_min;
|
|
||||||
|
|
||||||
// Update wave to spawn single bot
|
|
||||||
wave.slots_min = 1;
|
|
||||||
wave.slots_max = 2;
|
|
||||||
|
|
||||||
// Get index of wave
|
|
||||||
const indexOfWaveToSplit = location.base.waves.indexOf(wave);
|
|
||||||
this.logger.debug(
|
|
||||||
`Splitting map: ${location.base.Id} wave: ${indexOfWaveToSplit} with ${waveSize} bots`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add new waves to fill gap from bots we removed in above wave
|
|
||||||
let wavesAddedCount = 0;
|
|
||||||
for (let index = indexOfWaveToSplit + 1; index < indexOfWaveToSplit + waveSize; index++) {
|
|
||||||
// Clone wave ready to insert into array
|
|
||||||
const waveToAddClone = this.cloner.clone(wave);
|
|
||||||
|
|
||||||
// Some waves have value of 0 for some reason, preserve
|
|
||||||
if (waveToAddClone.number !== 0) {
|
|
||||||
// Update wave number to new location in array
|
|
||||||
waveToAddClone.number = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place wave into array in just-edited position + 1
|
|
||||||
location.base.waves.splice(index, 0, waveToAddClone);
|
|
||||||
wavesAddedCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update subsequent wave number property to accommodate the new waves
|
|
||||||
for (
|
|
||||||
let index = indexOfWaveToSplit + wavesAddedCount + 1;
|
|
||||||
index < location.base.waves.length;
|
|
||||||
index++
|
|
||||||
) {
|
|
||||||
// Some waves have value of 0, leave them as-is
|
|
||||||
if (location.base.waves[index].number !== 0) {
|
|
||||||
location.base.waves[index].number += wavesAddedCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of installed mods and save their details to the profile being used
|
* Get a list of installed mods and save their details to the profile being used
|
||||||
* @param fullProfile Profile to add mod details to
|
* @param fullProfile Profile to add mod details to
|
||||||
@ -862,44 +473,6 @@ export class GameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for any missing assorts inside each traders assort.json data, checking against traders questassort.json
|
|
||||||
*/
|
|
||||||
protected validateQuestAssortUnlocksExist(): void {
|
|
||||||
const db = this.databaseService.getTables();
|
|
||||||
const traders = db.traders;
|
|
||||||
const quests = db.templates.quests;
|
|
||||||
for (const traderId of Object.values(Traders)) {
|
|
||||||
const traderData = traders[traderId];
|
|
||||||
const traderAssorts = traderData?.assort;
|
|
||||||
if (!traderAssorts) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge started/success/fail quest assorts into one dictionary
|
|
||||||
const mergedQuestAssorts = {
|
|
||||||
...traderData.questassort?.started,
|
|
||||||
...traderData.questassort?.success,
|
|
||||||
...traderData.questassort?.fail,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Loop over all assorts for trader
|
|
||||||
for (const [assortKey, questKey] of Object.entries(mergedQuestAssorts)) {
|
|
||||||
// Does assort key exist in trader assort file
|
|
||||||
if (!traderAssorts.loyal_level_items[assortKey]) {
|
|
||||||
// Reverse lookup of enum key by value
|
|
||||||
const messageValues = {
|
|
||||||
traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)],
|
|
||||||
questName: quests[questKey]?.QuestName ?? "UNKNOWN",
|
|
||||||
};
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("assort-missing_quest_assort_unlock", messageValues),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add the logged in players name to PMC name pool
|
* Add the logged in players name to PMC name pool
|
||||||
* @param pmcProfile Profile of player to get name from
|
* @param pmcProfile Profile of player to get name from
|
||||||
@ -914,6 +487,11 @@ export class GameController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip if player name exists already
|
||||||
|
if (bots.bear?.firstName.some((x) => x === playerName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (bots.bear) {
|
if (bots.bear) {
|
||||||
bots.bear.firstName.push(playerName);
|
bots.bear.firstName.push(playerName);
|
||||||
}
|
}
|
||||||
@ -935,34 +513,6 @@ export class GameController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Blank out the "test" mail message from prapor
|
|
||||||
*/
|
|
||||||
protected removePraporTestMessage(): void {
|
|
||||||
// Iterate over all languages (e.g. "en", "fr")
|
|
||||||
const locales = this.databaseService.getLocales();
|
|
||||||
for (const localeKey in locales.global) {
|
|
||||||
locales.global[localeKey]["61687e2c3e526901fa76baf9"] = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make non-trigger-spawned raiders spawn earlier + always
|
|
||||||
*/
|
|
||||||
protected adjustLabsRaiderSpawnRate(): void {
|
|
||||||
const labsBase = this.databaseService.getLocations().laboratory.base;
|
|
||||||
|
|
||||||
// Find spawns with empty string for triggerId/TriggerName
|
|
||||||
const nonTriggerLabsBossSpawns = labsBase.BossLocationSpawn.filter(
|
|
||||||
(bossSpawn) => !bossSpawn.TriggerId && !bossSpawn.TriggerName,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const boss of nonTriggerLabsBossSpawns) {
|
|
||||||
boss.BossChance = 100;
|
|
||||||
boss.Time /= 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected logProfileDetails(fullProfile: ISptProfile): void {
|
protected logProfileDetails(fullProfile: ISptProfile): void {
|
||||||
this.logger.debug(`Profile made with: ${fullProfile.spt.version}`);
|
this.logger.debug(`Profile made with: ${fullProfile.spt.version}`);
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
|
@ -219,11 +219,6 @@ export class ProfileController {
|
|||||||
this.saveServer.getProfile(sessionID).info.wipe = false;
|
this.saveServer.getProfile(sessionID).info.wipe = false;
|
||||||
this.saveServer.saveProfile(sessionID);
|
this.saveServer.saveProfile(sessionID);
|
||||||
|
|
||||||
// Requires to enable seasonal changes after creating fresh profile
|
|
||||||
if (this.seasonalEventService.isAutomaticEventDetectionEnabled()) {
|
|
||||||
this.seasonalEventService.enableSeasonalEvents(sessionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pmcData._id;
|
return pmcData._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +226,7 @@ import { OpenZoneService } from "@spt/services/OpenZoneService";
|
|||||||
import { PaymentService } from "@spt/services/PaymentService";
|
import { PaymentService } from "@spt/services/PaymentService";
|
||||||
import { PlayerService } from "@spt/services/PlayerService";
|
import { PlayerService } from "@spt/services/PlayerService";
|
||||||
import { PmcChatResponseService } from "@spt/services/PmcChatResponseService";
|
import { PmcChatResponseService } from "@spt/services/PmcChatResponseService";
|
||||||
|
import { PostDbLoadService } from "@spt/services/PostDbLoadService";
|
||||||
import { ProfileActivityService } from "@spt/services/ProfileActivityService";
|
import { ProfileActivityService } from "@spt/services/ProfileActivityService";
|
||||||
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
import { ProfileFixerService } from "@spt/services/ProfileFixerService";
|
||||||
import { RagfairCategoriesService } from "@spt/services/RagfairCategoriesService";
|
import { RagfairCategoriesService } from "@spt/services/RagfairCategoriesService";
|
||||||
@ -807,6 +808,9 @@ export class Container {
|
|||||||
depContainer.register<RaidWeatherService>("RaidWeatherService", RaidWeatherService, {
|
depContainer.register<RaidWeatherService>("RaidWeatherService", RaidWeatherService, {
|
||||||
lifecycle: Lifecycle.Singleton,
|
lifecycle: Lifecycle.Singleton,
|
||||||
});
|
});
|
||||||
|
depContainer.register<PostDbLoadService>("PostDbLoadService", PostDbLoadService, {
|
||||||
|
lifecycle: Lifecycle.Singleton,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static registerServers(depContainer: DependencyContainer): void {
|
private static registerServers(depContainer: DependencyContainer): void {
|
||||||
|
504
project/src/services/PostDbLoadService.ts
Normal file
504
project/src/services/PostDbLoadService.ts
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
import { ILocation } from "@spt/models/eft/common/ILocation";
|
||||||
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
||||||
|
import { Traders } from "@spt/models/enums/Traders";
|
||||||
|
import { Weapons } from "@spt/models/enums/Weapons";
|
||||||
|
import { IBotConfig } from "@spt/models/spt/config/IBotConfig";
|
||||||
|
import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig";
|
||||||
|
import { IHideoutConfig } from "@spt/models/spt/config/IHideoutConfig";
|
||||||
|
import { ILocationConfig } from "@spt/models/spt/config/ILocationConfig";
|
||||||
|
import { ILootConfig } from "@spt/models/spt/config/ILootConfig";
|
||||||
|
import { IPmcConfig } from "@spt/models/spt/config/IPmcConfig";
|
||||||
|
import { IRagfairConfig } from "@spt/models/spt/config/IRagfairConfig";
|
||||||
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
||||||
|
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
||||||
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
|
import { ItemBaseClassService } from "@spt/services/ItemBaseClassService";
|
||||||
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
|
import { OpenZoneService } from "@spt/services/OpenZoneService";
|
||||||
|
import { SeasonalEventService } from "@spt/services/SeasonalEventService";
|
||||||
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class PostDbLoadService {
|
||||||
|
protected coreConfig: ICoreConfig;
|
||||||
|
protected locationConfig: ILocationConfig;
|
||||||
|
protected ragfairConfig: IRagfairConfig;
|
||||||
|
protected hideoutConfig: IHideoutConfig;
|
||||||
|
protected pmcConfig: IPmcConfig;
|
||||||
|
protected lootConfig: ILootConfig;
|
||||||
|
protected botConfig: IBotConfig;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("CustomLocationWaveService") protected customLocationWaveService: CustomLocationWaveService,
|
||||||
|
@inject("OpenZoneService") protected openZoneService: OpenZoneService,
|
||||||
|
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||||
|
@inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService,
|
||||||
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
) {
|
||||||
|
this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE);
|
||||||
|
this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION);
|
||||||
|
this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR);
|
||||||
|
this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT);
|
||||||
|
this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC);
|
||||||
|
this.lootConfig = this.configServer.getConfig(ConfigTypes.LOOT);
|
||||||
|
this.botConfig = this.configServer.getConfig(ConfigTypes.BOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public performPostDbLoadActions(): void {
|
||||||
|
// Regenerate base cache now mods are loaded and game is starting
|
||||||
|
// Mods that add items and use the baseClass service generate the cache including their items, the next mod that
|
||||||
|
// add items gets left out,causing warnings
|
||||||
|
this.itemBaseClassService.hydrateItemBaseClassCache();
|
||||||
|
|
||||||
|
this.addCustomLooseLootPositions();
|
||||||
|
|
||||||
|
if (this.coreConfig.fixes.fixShotgunDispersion) {
|
||||||
|
this.fixShotgunDispersions();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.locationConfig.addOpenZonesToAllMaps) {
|
||||||
|
this.openZoneService.applyZoneChangesToAllMaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.locationConfig.addCustomBotWavesToMaps) {
|
||||||
|
this.customLocationWaveService.applyWaveChangesToAllMaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.locationConfig.enableBotTypeLimits) {
|
||||||
|
this.adjustMapBotLimits();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adjustLooseLootSpawnProbabilities();
|
||||||
|
|
||||||
|
this.checkTraderRepairValuesExist();
|
||||||
|
|
||||||
|
this.adjustLocationBotValues();
|
||||||
|
|
||||||
|
if (this.locationConfig.fixEmptyBotWavesSettings.enabled) {
|
||||||
|
this.fixBrokenOfflineMapWaves();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.locationConfig.rogueLighthouseSpawnTimeSettings.enabled) {
|
||||||
|
this.fixRoguesSpawningInstantlyOnLighthouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.enabled) {
|
||||||
|
this.splitBotWavesIntoSingleWaves();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adjustLabsRaiderSpawnRate();
|
||||||
|
|
||||||
|
this.adjustHideoutCraftTimes(this.hideoutConfig.overrideCraftTimeSeconds);
|
||||||
|
this.adjustHideoutBuildTimes(this.hideoutConfig.overrideBuildTimeSeconds);
|
||||||
|
|
||||||
|
this.removePraporTestMessage();
|
||||||
|
|
||||||
|
this.validateQuestAssortUnlocksExist();
|
||||||
|
|
||||||
|
if (this.seasonalEventService.isAutomaticEventDetectionEnabled()) {
|
||||||
|
this.seasonalEventService.enableSeasonalEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flea bsg blacklist is off
|
||||||
|
if (!this.ragfairConfig.dynamic.blacklist.enableBsgList) {
|
||||||
|
this.setAllDbItemsAsSellableOnFlea();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addCustomLooseLootPositions(): void {
|
||||||
|
const looseLootPositionsToAdd = this.lootConfig.looseLoot;
|
||||||
|
for (const [mapId, positionsToAdd] of Object.entries(looseLootPositionsToAdd)) {
|
||||||
|
if (!mapId) {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("location-unable_to_add_custom_loot_position", mapId),
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapLooseLoot = this.databaseService.getLocation(mapId).looseLoot;
|
||||||
|
if (!mapLooseLoot) {
|
||||||
|
this.logger.warning(this.localisationService.getText("location-map_has_no_loose_loot_data", mapId));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const positionToAdd of positionsToAdd) {
|
||||||
|
// Exists already, add new items to existing positions pool
|
||||||
|
const existingLootPosition = mapLooseLoot.spawnpoints.find(
|
||||||
|
(x) => x.template.Id === positionToAdd.template.Id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingLootPosition) {
|
||||||
|
existingLootPosition.template.Items.push(...positionToAdd.template.Items);
|
||||||
|
existingLootPosition.itemDistribution.push(...positionToAdd.itemDistribution);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// New position, add entire object
|
||||||
|
mapLooseLoot.spawnpoints.push(positionToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BSG have two values for shotgun dispersion, we make sure both have the same value
|
||||||
|
*/
|
||||||
|
protected fixShotgunDispersions(): void {
|
||||||
|
const itemDb = this.databaseService.getItems();
|
||||||
|
|
||||||
|
const shotguns = [Weapons.SHOTGUN_12G_SAIGA_12K, Weapons.SHOTGUN_20G_TOZ_106, Weapons.SHOTGUN_12G_M870];
|
||||||
|
for (const shotgunId of shotguns) {
|
||||||
|
if (itemDb[shotgunId]._props.ShotgunDispersion) {
|
||||||
|
itemDb[shotgunId]._props.shotgunDispersion = itemDb[shotgunId]._props.ShotgunDispersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply custom limits on bot types as defined in configs/location.json/botTypeLimits */
|
||||||
|
protected adjustMapBotLimits(): void {
|
||||||
|
const mapsDb = this.databaseService.getLocations();
|
||||||
|
if (!this.locationConfig.botTypeLimits) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const mapId in this.locationConfig.botTypeLimits) {
|
||||||
|
const map: ILocation = mapsDb[mapId];
|
||||||
|
if (!map) {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("bot-unable_to_edit_limits_of_unknown_map", mapId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const botToLimit of this.locationConfig.botTypeLimits[mapId]) {
|
||||||
|
const index = map.base.MinMaxBots.findIndex((x) => x.WildSpawnType === botToLimit.type);
|
||||||
|
if (index !== -1) {
|
||||||
|
// Existing bot type found in MinMaxBots array, edit
|
||||||
|
const limitObjectToUpdate = map.base.MinMaxBots[index];
|
||||||
|
limitObjectToUpdate.min = botToLimit.min;
|
||||||
|
limitObjectToUpdate.max = botToLimit.max;
|
||||||
|
} else {
|
||||||
|
// Bot type not found, add new object
|
||||||
|
map.base.MinMaxBots.push({
|
||||||
|
// Bot type not found, add new object
|
||||||
|
WildSpawnType: botToLimit.type,
|
||||||
|
min: botToLimit.min,
|
||||||
|
max: botToLimit.max,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected adjustLooseLootSpawnProbabilities(): void {
|
||||||
|
if (!this.lootConfig.looseLootSpawnPointAdjustments) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [mapId, mapAdjustments] of Object.entries(this.lootConfig.looseLootSpawnPointAdjustments)) {
|
||||||
|
const mapLooseLootData = this.databaseService.getLocation(mapId).looseLoot;
|
||||||
|
if (!mapLooseLootData) {
|
||||||
|
this.logger.warning(this.localisationService.getText("location-map_has_no_loose_loot_data", mapId));
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [lootKey, newChanceValue] of Object.entries(mapAdjustments)) {
|
||||||
|
const lootPostionToAdjust = mapLooseLootData.spawnpoints.find(
|
||||||
|
(spawnPoint) => spawnPoint.template.Id === lootKey,
|
||||||
|
);
|
||||||
|
if (!lootPostionToAdjust) {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("location-unable_to_adjust_loot_position_on_map", {
|
||||||
|
lootKey: lootKey,
|
||||||
|
mapId: mapId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lootPostionToAdjust.probability = newChanceValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Out of date/incorrectly made trader mods forget this data
|
||||||
|
*/
|
||||||
|
protected checkTraderRepairValuesExist(): void {
|
||||||
|
const traders = this.databaseService.getTraders();
|
||||||
|
for (const trader of Object.values(traders)) {
|
||||||
|
if (!trader?.base?.repair) {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("trader-missing_repair_property_using_default", {
|
||||||
|
traderId: trader.base._id,
|
||||||
|
nickname: trader.base.nickname,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// use ragfair trader as a default
|
||||||
|
trader.base.repair = this.cloner.clone(traders.ragfair.base.repair);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trader.base.repair?.quality === undefined) {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("trader-missing_repair_quality_property_using_default", {
|
||||||
|
traderId: trader.base._id,
|
||||||
|
nickname: trader.base.nickname,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// use ragfair trader as a default
|
||||||
|
trader.base.repair.quality = this.cloner.clone(traders.ragfair.base.repair.quality);
|
||||||
|
trader.base.repair.quality = traders.ragfair.base.repair.quality;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected adjustLocationBotValues(): void {
|
||||||
|
const mapsDb = this.databaseService.getLocations();
|
||||||
|
|
||||||
|
for (const locationKey in this.botConfig.maxBotCap) {
|
||||||
|
const map: ILocation = mapsDb[locationKey];
|
||||||
|
if (!map) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.base.BotMaxPvE = this.botConfig.maxBotCap[locationKey];
|
||||||
|
|
||||||
|
// make values no larger than 30 secs
|
||||||
|
map.base.BotStart = Math.min(map.base.BotStart, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waves with an identical min/max values spawn nothing, the number of bots that spawn is the difference between min and max
|
||||||
|
*/
|
||||||
|
protected fixBrokenOfflineMapWaves(): void {
|
||||||
|
const locations = this.databaseService.getLocations();
|
||||||
|
for (const locationKey in locations) {
|
||||||
|
// Skip ignored maps
|
||||||
|
if (this.locationConfig.fixEmptyBotWavesSettings.ignoreMaps.includes(locationKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all of the locations waves and look for waves with identical min and max slots
|
||||||
|
const location: ILocation = locations[locationKey];
|
||||||
|
if (!location.base) {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("location-unable_to_fix_broken_waves_missing_base", locationKey),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wave of location.base.waves ?? []) {
|
||||||
|
if (wave.slots_max - wave.slots_min === 0) {
|
||||||
|
this.logger.debug(
|
||||||
|
`Fixed ${wave.WildSpawnType} Spawn: ${locationKey} wave: ${wave.number} of type: ${wave.WildSpawnType} in zone: ${wave.SpawnPoints} with Max Slots of ${wave.slots_max}`,
|
||||||
|
);
|
||||||
|
wave.slots_max++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make Rogues spawn later to allow for scavs to spawn first instead of rogues filling up all spawn positions
|
||||||
|
*/
|
||||||
|
protected fixRoguesSpawningInstantlyOnLighthouse(): void {
|
||||||
|
const rogueSpawnDelaySeconds = this.locationConfig.rogueLighthouseSpawnTimeSettings.waitTimeSeconds;
|
||||||
|
const lighthouse = this.databaseService.getLocations().lighthouse?.base;
|
||||||
|
if (!lighthouse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find Rogues that spawn instantly
|
||||||
|
const instantRogueBossSpawns = lighthouse.BossLocationSpawn.filter(
|
||||||
|
(spawn) => spawn.BossName === "exUsec" && spawn.Time === -1,
|
||||||
|
);
|
||||||
|
for (const wave of instantRogueBossSpawns) {
|
||||||
|
wave.Time = rogueSpawnDelaySeconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and split waves with large numbers of bots into smaller waves - BSG appears to reduce the size of these
|
||||||
|
* waves to one bot when they're waiting to spawn for too long
|
||||||
|
*/
|
||||||
|
protected splitBotWavesIntoSingleWaves(): void {
|
||||||
|
const locations = this.databaseService.getLocations();
|
||||||
|
for (const locationKey in locations) {
|
||||||
|
if (this.locationConfig.splitWaveIntoSingleSpawnsSettings.ignoreMaps.includes(locationKey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all maps
|
||||||
|
const location: ILocation = locations[locationKey];
|
||||||
|
for (const wave of location.base.waves) {
|
||||||
|
// Wave has size that makes it candidate for splitting
|
||||||
|
if (
|
||||||
|
wave.slots_max - wave.slots_min >=
|
||||||
|
this.locationConfig.splitWaveIntoSingleSpawnsSettings.waveSizeThreshold
|
||||||
|
) {
|
||||||
|
// Get count of bots to be spawned in wave
|
||||||
|
const waveSize = wave.slots_max - wave.slots_min;
|
||||||
|
|
||||||
|
// Update wave to spawn single bot
|
||||||
|
wave.slots_min = 1;
|
||||||
|
wave.slots_max = 2;
|
||||||
|
|
||||||
|
// Get index of wave
|
||||||
|
const indexOfWaveToSplit = location.base.waves.indexOf(wave);
|
||||||
|
this.logger.debug(
|
||||||
|
`Splitting map: ${location.base.Id} wave: ${indexOfWaveToSplit} with ${waveSize} bots`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add new waves to fill gap from bots we removed in above wave
|
||||||
|
let wavesAddedCount = 0;
|
||||||
|
for (let index = indexOfWaveToSplit + 1; index < indexOfWaveToSplit + waveSize; index++) {
|
||||||
|
// Clone wave ready to insert into array
|
||||||
|
const waveToAddClone = this.cloner.clone(wave);
|
||||||
|
|
||||||
|
// Some waves have value of 0 for some reason, preserve
|
||||||
|
if (waveToAddClone.number !== 0) {
|
||||||
|
// Update wave number to new location in array
|
||||||
|
waveToAddClone.number = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place wave into array in just-edited position + 1
|
||||||
|
location.base.waves.splice(index, 0, waveToAddClone);
|
||||||
|
wavesAddedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update subsequent wave number property to accommodate the new waves
|
||||||
|
for (
|
||||||
|
let index = indexOfWaveToSplit + wavesAddedCount + 1;
|
||||||
|
index < location.base.waves.length;
|
||||||
|
index++
|
||||||
|
) {
|
||||||
|
// Some waves have value of 0, leave them as-is
|
||||||
|
if (location.base.waves[index].number !== 0) {
|
||||||
|
location.base.waves[index].number += wavesAddedCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make non-trigger-spawned raiders spawn earlier + always
|
||||||
|
*/
|
||||||
|
protected adjustLabsRaiderSpawnRate(): void {
|
||||||
|
const labsBase = this.databaseService.getLocations().laboratory.base;
|
||||||
|
|
||||||
|
// Find spawns with empty string for triggerId/TriggerName
|
||||||
|
const nonTriggerLabsBossSpawns = labsBase.BossLocationSpawn.filter(
|
||||||
|
(bossSpawn) => !bossSpawn.TriggerId && !bossSpawn.TriggerName,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const boss of nonTriggerLabsBossSpawns) {
|
||||||
|
boss.BossChance = 100;
|
||||||
|
boss.Time /= 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected adjustHideoutCraftTimes(overrideSeconds: number): void {
|
||||||
|
if (overrideSeconds === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const craft of this.databaseService.getHideout().production.recipes) {
|
||||||
|
// Only adjust crafts ABOVE the override
|
||||||
|
craft.productionTime = Math.min(craft.productionTime, overrideSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust all hideout craft times to be no higher than the override
|
||||||
|
*/
|
||||||
|
protected adjustHideoutBuildTimes(overrideSeconds: number): void {
|
||||||
|
if (overrideSeconds === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const area of this.databaseService.getHideout().areas) {
|
||||||
|
for (const stage of Object.values(area.stages)) {
|
||||||
|
// Only adjust crafts ABOVE the override
|
||||||
|
stage.constructionTime = Math.min(stage.constructionTime, overrideSeconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blank out the "test" mail message from prapor
|
||||||
|
*/
|
||||||
|
protected removePraporTestMessage(): void {
|
||||||
|
// Iterate over all languages (e.g. "en", "fr")
|
||||||
|
const locales = this.databaseService.getLocales();
|
||||||
|
for (const localeKey in locales.global) {
|
||||||
|
locales.global[localeKey]["61687e2c3e526901fa76baf9"] = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for any missing assorts inside each traders assort.json data, checking against traders questassort.json
|
||||||
|
*/
|
||||||
|
protected validateQuestAssortUnlocksExist(): void {
|
||||||
|
const db = this.databaseService.getTables();
|
||||||
|
const traders = db.traders;
|
||||||
|
const quests = db.templates.quests;
|
||||||
|
for (const traderId of Object.values(Traders)) {
|
||||||
|
const traderData = traders[traderId];
|
||||||
|
const traderAssorts = traderData?.assort;
|
||||||
|
if (!traderAssorts) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge started/success/fail quest assorts into one dictionary
|
||||||
|
const mergedQuestAssorts = {
|
||||||
|
...traderData.questassort?.started,
|
||||||
|
...traderData.questassort?.success,
|
||||||
|
...traderData.questassort?.fail,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop over all assorts for trader
|
||||||
|
for (const [assortKey, questKey] of Object.entries(mergedQuestAssorts)) {
|
||||||
|
// Does assort key exist in trader assort file
|
||||||
|
if (!traderAssorts.loyal_level_items[assortKey]) {
|
||||||
|
// Reverse lookup of enum key by value
|
||||||
|
const messageValues = {
|
||||||
|
traderName: Object.keys(Traders)[Object.values(Traders).indexOf(traderId)],
|
||||||
|
questName: quests[questKey]?.QuestName ?? "UNKNOWN",
|
||||||
|
};
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("assort-missing_quest_assort_unlock", messageValues),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setAllDbItemsAsSellableOnFlea(): void {
|
||||||
|
const dbItems = Object.values(this.databaseService.getItems());
|
||||||
|
for (const item of dbItems) {
|
||||||
|
if (
|
||||||
|
item._type === "Item" &&
|
||||||
|
!item._props?.CanSellOnRagfair &&
|
||||||
|
!this.ragfairConfig.dynamic.blacklist.custom.includes(item._id)
|
||||||
|
) {
|
||||||
|
item._props.CanSellOnRagfair = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -197,13 +197,12 @@ export class SeasonalEventService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle seasonal events
|
* Handle seasonal events
|
||||||
* @param sessionId Players id
|
|
||||||
*/
|
*/
|
||||||
public enableSeasonalEvents(sessionId: string): void {
|
public enableSeasonalEvents(): void {
|
||||||
if (this.currentlyActiveEvents) {
|
if (this.currentlyActiveEvents) {
|
||||||
const globalConfig = this.databaseService.getGlobals().config;
|
const globalConfig = this.databaseService.getGlobals().config;
|
||||||
for (const event of this.currentlyActiveEvents) {
|
for (const event of this.currentlyActiveEvents) {
|
||||||
this.updateGlobalEvents(sessionId, globalConfig, event);
|
this.updateGlobalEvents(globalConfig, event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,11 +324,10 @@ export class SeasonalEventService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Make adjusted to server code based on the name of the event passed in
|
* Make adjusted to server code based on the name of the event passed in
|
||||||
* @param sessionId Player id
|
|
||||||
* @param globalConfig globals.json
|
* @param globalConfig globals.json
|
||||||
* @param eventName Name of the event to enable. e.g. Christmas
|
* @param eventName Name of the event to enable. e.g. Christmas
|
||||||
*/
|
*/
|
||||||
protected updateGlobalEvents(sessionId: string, globalConfig: IConfig, eventType: SeasonalEventType): void {
|
protected updateGlobalEvents(globalConfig: IConfig, eventType: SeasonalEventType): void {
|
||||||
this.logger.success(`${eventType} event is active`);
|
this.logger.success(`${eventType} event is active`);
|
||||||
|
|
||||||
switch (eventType.toLowerCase()) {
|
switch (eventType.toLowerCase()) {
|
||||||
@ -352,11 +350,9 @@ export class SeasonalEventService {
|
|||||||
this.addGifterBotToMaps();
|
this.addGifterBotToMaps();
|
||||||
this.addLootItemsToGifterDropItemsList();
|
this.addLootItemsToGifterDropItemsList();
|
||||||
this.enableDancingTree();
|
this.enableDancingTree();
|
||||||
this.giveGift(sessionId, "Christmas2022");
|
|
||||||
this.enableSnow();
|
this.enableSnow();
|
||||||
break;
|
break;
|
||||||
case SeasonalEventType.NEW_YEARS.toLowerCase():
|
case SeasonalEventType.NEW_YEARS.toLowerCase():
|
||||||
this.giveGift(sessionId, "NewYear2023");
|
|
||||||
this.enableSnow();
|
this.enableSnow();
|
||||||
break;
|
break;
|
||||||
case SeasonalEventType.SNOW.toLowerCase():
|
case SeasonalEventType.SNOW.toLowerCase():
|
||||||
@ -369,6 +365,22 @@ export class SeasonalEventService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public givePlayerSeasonalGifts(sessionId: string): void {
|
||||||
|
if (this.currentlyActiveEvents) {
|
||||||
|
const globalConfig = this.databaseService.getGlobals().config;
|
||||||
|
for (const event of this.currentlyActiveEvents) {
|
||||||
|
switch (event.toLowerCase()) {
|
||||||
|
case SeasonalEventType.CHRISTMAS.toLowerCase():
|
||||||
|
this.giveGift(sessionId, "Christmas2022");
|
||||||
|
break;
|
||||||
|
case SeasonalEventType.NEW_YEARS.toLowerCase():
|
||||||
|
this.giveGift(sessionId, "NewYear2023");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force zryachiy to always have a melee weapon
|
* Force zryachiy to always have a melee weapon
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user