diff --git a/project/src/controllers/BotController.ts b/project/src/controllers/BotController.ts index 44bfc930..050bda12 100644 --- a/project/src/controllers/BotController.ts +++ b/project/src/controllers/BotController.ts @@ -24,6 +24,7 @@ import { BotGenerationCacheService } from "@spt-aki/services/BotGenerationCacheS import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { MatchBotDetailsCacheService } from "@spt-aki/services/MatchBotDetailsCacheService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -48,6 +49,7 @@ export class BotController @inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -281,7 +283,7 @@ export class BotController const botPromises: Promise[] = []; for (let i = 0; i < botGenerationDetails.botCountToGenerate; i++) { - const detailsClone = this.jsonUtil.clone(botGenerationDetails); + const detailsClone = this.cloner.clone(botGenerationDetails); botPromises.push(this.generateSingleBotAndStoreInCache(detailsClone, sessionId, cacheKey)); } return Promise.all(botPromises).then(() => diff --git a/project/src/controllers/BuildController.ts b/project/src/controllers/BuildController.ts index b1cee1fa..d9c9880e 100644 --- a/project/src/controllers/BuildController.ts +++ b/project/src/controllers/BuildController.ts @@ -10,6 +10,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { SaveServer } from "@spt-aki/servers/SaveServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -25,6 +26,7 @@ export class BuildController @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("SaveServer") protected saveServer: SaveServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -39,7 +41,7 @@ export class BuildController } // Ensure the secure container in the default presets match what the player has equipped - const defaultEquipmentPresetsClone = this.jsonUtil.clone( + const defaultEquipmentPresetsClone = this.cloner.clone( this.databaseServer.getTables().templates.defaultEquipmentPresets, ); const playerSecureContainer = profile.characters.pmc.Inventory.items?.find(x => @@ -63,7 +65,7 @@ export class BuildController } // Clone player build data from profile and append the above defaults onto end - const userBuildsClone = this.jsonUtil.clone(profile.userbuilds); + const userBuildsClone = this.cloner.clone(profile.userbuilds); userBuildsClone.equipmentBuilds.push(...defaultEquipmentPresetsClone); return userBuildsClone; diff --git a/project/src/controllers/GameController.ts b/project/src/controllers/GameController.ts index 04b45bf2..913d6b66 100644 --- a/project/src/controllers/GameController.ts +++ b/project/src/controllers/GameController.ts @@ -44,6 +44,7 @@ import { ProfileActivityService } from "@spt-aki/services/ProfileActivityService import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -83,6 +84,7 @@ export class GameController @inject("ProfileActivityService") protected profileActivityService: ProfileActivityService, @inject("ApplicationContext") protected applicationContext: ApplicationContext, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.httpConfig = this.configServer.getConfig(ConfigTypes.HTTP); @@ -321,7 +323,7 @@ export class GameController this.logger.warning( `Trader ${trader.base._id} ${trader.base.nickname} is missing a repair object, adding in default values`, ); - trader.base.repair = this.jsonUtil.clone(this.databaseServer.getTables().traders.ragfair.base.repair); + trader.base.repair = this.cloner.clone(this.databaseServer.getTables().traders.ragfair.base.repair); return; } @@ -331,7 +333,7 @@ export class GameController this.logger.warning( `Trader ${trader.base._id} ${trader.base.nickname} is missing a repair quality value, adding in default value`, ); - trader.base.repair.quality = this.jsonUtil.clone( + trader.base.repair.quality = this.cloner.clone( this.databaseServer.getTables().traders.ragfair.base.repair.quality, ); trader.base.repair.quality = this.databaseServer.getTables().traders.ragfair.base.repair.quality; @@ -791,7 +793,7 @@ export class GameController for (let index = indexOfWaveToSplit + 1; index < indexOfWaveToSplit + waveSize; index++) { // Clone wave ready to insert into array - const waveToAddClone = this.jsonUtil.clone(wave); + const waveToAddClone = this.cloner.clone(wave); // Some waves have value of 0 for some reason, preserve if (waveToAddClone.number !== 0) diff --git a/project/src/controllers/HealthController.ts b/project/src/controllers/HealthController.ts index a8f2aec2..c7bcd011 100644 --- a/project/src/controllers/HealthController.ts +++ b/project/src/controllers/HealthController.ts @@ -15,6 +15,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { PaymentService } from "@spt-aki/services/PaymentService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -31,6 +32,7 @@ export class HealthController @inject("LocalisationService") protected localisationService: LocalisationService, @inject("HttpResponseUtil") protected httpResponse: HttpResponseUtil, @inject("HealthHelper") protected healthHelper: HealthHelper, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -215,7 +217,7 @@ export class HealthController } // Inform client of new post-raid, post-therapist heal values - output.profileChanges[sessionID].health = this.jsonUtil.clone(pmcData.Health); + output.profileChanges[sessionID].health = this.cloner.clone(pmcData.Health); return output; } diff --git a/project/src/controllers/HideoutController.ts b/project/src/controllers/HideoutController.ts index 66ab043a..1a9997eb 100644 --- a/project/src/controllers/HideoutController.ts +++ b/project/src/controllers/HideoutController.ts @@ -48,6 +48,7 @@ import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { PlayerService } from "@spt-aki/services/PlayerService"; import { ProfileActivityService } from "@spt-aki/services/ProfileActivityService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -83,6 +84,7 @@ export class HideoutController @inject("ConfigServer") protected configServer: ConfigServer, @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("FenceService") protected fenceService: FenceService, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT); @@ -594,7 +596,7 @@ export class HideoutController const recipe = this.databaseServer.getTables().hideout.production.find(p => p._id === body.recipeId); // Find the actual amount of items we need to remove because body can send weird data - const recipeRequirementsClone = this.jsonUtil.clone( + const recipeRequirementsClone = this.cloner.clone( recipe.requirements.filter(i => i.type === "Item" || i.type === "Tool"), ); diff --git a/project/src/controllers/InsuranceController.ts b/project/src/controllers/InsuranceController.ts index 7c2ebd50..852ad00d 100644 --- a/project/src/controllers/InsuranceController.ts +++ b/project/src/controllers/InsuranceController.ts @@ -56,6 +56,7 @@ export class InsuranceController @inject("MailSendService") protected mailSendService: MailSendService, @inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE); @@ -458,7 +459,7 @@ export class InsuranceController const countOfAttachmentsToRemove = this.getAttachmentCountToRemove(weightedAttachmentByPrice, traderId); // Create prob array and add all attachments with rouble price as the weight - const attachmentsProbabilityArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const attachmentsProbabilityArray = new ProbabilityObjectArray(this.mathUtil, this.cloner); for (const attachmentTpl of Object.keys(weightedAttachmentByPrice)) { attachmentsProbabilityArray.push( diff --git a/project/src/controllers/InventoryController.ts b/project/src/controllers/InventoryController.ts index 0c2f7f57..b2e5abbf 100644 --- a/project/src/controllers/InventoryController.ts +++ b/project/src/controllers/InventoryController.ts @@ -41,6 +41,7 @@ import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { PlayerService } from "@spt-aki/services/PlayerService"; import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -69,6 +70,7 @@ export class InventoryController @inject("LootGenerator") protected lootGenerator: LootGenerator, @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, @inject("HttpResponseUtil") protected httpResponseUtil: HttpResponseUtil, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -212,7 +214,7 @@ export class InventoryController } // Create new upd object that retains properties of original upd + new stack count size - const updatedUpd = this.jsonUtil.clone(itemToSplit.upd); + const updatedUpd = this.cloner.clone(itemToSplit.upd); updatedUpd.StackObjectsCount = request.count; // Remove split item count from source stack diff --git a/project/src/controllers/LocationController.ts b/project/src/controllers/LocationController.ts index b1c1f436..47174cd8 100644 --- a/project/src/controllers/LocationController.ts +++ b/project/src/controllers/LocationController.ts @@ -23,6 +23,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { RaidTimeAdjustmentService } from "@spt-aki/services/RaidTimeAdjustmentService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -49,6 +50,7 @@ export class LocationController @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("ConfigServer") protected configServer: ConfigServer, @inject("ApplicationContext") protected applicationContext: ApplicationContext, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.airdropConfig = this.configServer.getConfig(ConfigTypes.AIRDROP); @@ -80,7 +82,7 @@ export class LocationController { const db = this.databaseServer.getTables(); const location: ILocation = db.locations[name]; - const locationBaseClone: ILocationBase = this.jsonUtil.clone(location.base); + const locationBaseClone: ILocationBase = this.cloner.clone(location.base); locationBaseClone.UnixDateTime = this.timeUtil.getTimestamp(); @@ -97,18 +99,18 @@ export class LocationController >(); if (raidAdjustments) { - locationConfigCopy = this.jsonUtil.clone(this.locationConfig); // Clone values so they can be used to reset originals later + locationConfigCopy = this.cloner.clone(this.locationConfig); // Clone values so they can be used to reset originals later this.raidTimeAdjustmentService.makeAdjustmentsToMap(raidAdjustments, locationBaseClone); } - const staticAmmoDist = this.jsonUtil.clone(location.staticAmmo); + const staticAmmoDist = this.cloner.clone(location.staticAmmo); // Create containers and add loot to them const staticLoot = this.locationGenerator.generateStaticContainers(locationBaseClone, staticAmmoDist); locationBaseClone.Loot.push(...staticLoot); // Add dynamic loot to output loot - const dynamicLootDistClone: ILooseLoot = this.jsonUtil.clone(location.looseLoot); + const dynamicLootDistClone: ILooseLoot = this.cloner.clone(location.looseLoot); const dynamicSpawnPoints: SpawnpointTemplate[] = this.locationGenerator.generateDynamicLoot( dynamicLootDistClone, staticAmmoDist, diff --git a/project/src/controllers/QuestController.ts b/project/src/controllers/QuestController.ts index 766c246c..ea369b9b 100644 --- a/project/src/controllers/QuestController.ts +++ b/project/src/controllers/QuestController.ts @@ -29,6 +29,7 @@ import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { MailSendService } from "@spt-aki/services/MailSendService"; import { PlayerService } from "@spt-aki/services/PlayerService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -57,6 +58,7 @@ export class QuestController @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -466,10 +468,10 @@ export class QuestController const completeQuestResponse = this.eventOutputHolder.getOutput(sessionID); const completedQuest = this.questHelper.getQuestFromDb(body.qid, pmcData); - const preCompleteProfileQuests = this.jsonUtil.clone(pmcData.Quests); + const preCompleteProfileQuests = this.cloner.clone(pmcData.Quests); const completedQuestId = body.qid; - const clientQuestsClone = this.jsonUtil.clone(this.getClientQuests(sessionID)); // Must be gathered prior to applyQuestReward() & failQuests() + const clientQuestsClone = this.cloner.clone(this.getClientQuests(sessionID)); // Must be gathered prior to applyQuestReward() & failQuests() const newQuestState = QuestStatus.Success; this.questHelper.updateQuestState(pmcData, newQuestState, completedQuestId); diff --git a/project/src/controllers/RepeatableQuestController.ts b/project/src/controllers/RepeatableQuestController.ts index aabacbe3..c48b9acf 100644 --- a/project/src/controllers/RepeatableQuestController.ts +++ b/project/src/controllers/RepeatableQuestController.ts @@ -26,6 +26,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { PaymentService } from "@spt-aki/services/PaymentService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { ObjectId } from "@spt-aki/utils/ObjectId"; @@ -53,6 +54,7 @@ export class RepeatableQuestController @inject("RepeatableQuestHelper") protected repeatableQuestHelper: RepeatableQuestHelper, @inject("QuestHelper") protected questHelper: QuestHelper, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -458,7 +460,7 @@ export class RepeatableQuestController ); // Get cost to replace existing quest - changeRequirement = this.jsonUtil.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]); + changeRequirement = this.cloner.clone(currentRepeatablePool.changeRequirement[changeRequest.qid]); delete currentRepeatablePool.changeRequirement[changeRequest.qid]; // TODO: somehow we need to reduce the questPool by the currently active quests (for all repeatables) @@ -490,7 +492,7 @@ export class RepeatableQuestController } // Found and replaced the quest in current repeatable - repeatableToChange = this.jsonUtil.clone(currentRepeatablePool); + repeatableToChange = this.cloner.clone(currentRepeatablePool); delete repeatableToChange.inactiveQuests; break; diff --git a/project/src/controllers/TraderController.ts b/project/src/controllers/TraderController.ts index 5f950509..bba6fad1 100644 --- a/project/src/controllers/TraderController.ts +++ b/project/src/controllers/TraderController.ts @@ -13,6 +13,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { FenceService } from "@spt-aki/services/FenceService"; import { TraderAssortService } from "@spt-aki/services/TraderAssortService"; import { TraderPurchasePersisterService } from "@spt-aki/services/TraderPurchasePersisterService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -34,6 +35,7 @@ export class TraderController @inject("FenceBaseAssortGenerator") protected fenceBaseAssortGenerator: FenceBaseAssortGenerator, @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); @@ -67,7 +69,7 @@ export class TraderController // Create dict of trader assorts on server start if (!this.traderAssortService.getPristineTraderAssort(traderId)) { - const assortsClone = this.jsonUtil.clone(trader.assort); + const assortsClone = this.cloner.clone(trader.assort); this.traderAssortService.setPristineTraderAssort(traderId, assortsClone); } diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index cdf23076..dd31450d 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -240,6 +240,10 @@ import { TraderPurchasePersisterService } from "@spt-aki/services/TraderPurchase import { TraderServicesService } from "@spt-aki/services/TraderServicesService"; import { App } from "@spt-aki/utils/App"; import { AsyncQueue } from "@spt-aki/utils/AsyncQueue"; +import type { ICloner } from "@spt-aki/utils/cloners/ICloner"; +import { JsonCloner } from "@spt-aki/utils/cloners/JsonCloner"; +import { RecursiveCloner } from "@spt-aki/utils/cloners/RecursiveCloner"; +import { StructuredCloner } from "@spt-aki/utils/cloners/StructuredCloner"; import { CompareUtil } from "@spt-aki/utils/CompareUtil"; import { DatabaseImporter } from "@spt-aki/utils/DatabaseImporter"; import { EncodingUtil } from "@spt-aki/utils/EncodingUtil"; @@ -411,6 +415,9 @@ export class Container depContainer.register("ModLoadOrder", ModLoadOrder, { lifecycle: Lifecycle.Singleton }); depContainer.register("ModTypeCheck", ModTypeCheck, { lifecycle: Lifecycle.Singleton }); depContainer.register("CompareUtil", CompareUtil, { lifecycle: Lifecycle.Singleton }); + depContainer.register("StructuredCloner", StructuredCloner, { lifecycle: Lifecycle.Singleton }); + depContainer.register("JsonCloner", JsonCloner, { lifecycle: Lifecycle.Singleton }); + depContainer.register("RecursiveCloner", RecursiveCloner, { lifecycle: Lifecycle.Singleton }); } private static registerRouters(depContainer: DependencyContainer): void diff --git a/project/src/generators/BotEquipmentModGenerator.ts b/project/src/generators/BotEquipmentModGenerator.ts index 5d35d29a..108ddf9a 100644 --- a/project/src/generators/BotEquipmentModGenerator.ts +++ b/project/src/generators/BotEquipmentModGenerator.ts @@ -25,6 +25,7 @@ import { BotEquipmentModPoolService } from "@spt-aki/services/BotEquipmentModPoo import { BotModLimits, BotWeaponModLimitService } from "@spt-aki/services/BotWeaponModLimitService"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -56,6 +57,7 @@ export class BotEquipmentModGenerator @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotEquipmentModPoolService") protected botEquipmentModPoolService: BotEquipmentModPoolService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -157,7 +159,7 @@ export class BotEquipmentModGenerator // Find random mod and check its compatible let modTpl: string; let found = false; - const exhaustableModPool = new ExhaustableArray(modPoolToChooseFrom, this.randomUtil, this.jsonUtil); + const exhaustableModPool = new ExhaustableArray(modPoolToChooseFrom, this.randomUtil, this.cloner); while (exhaustableModPool.hasValues()) { modTpl = exhaustableModPool.getRandomValue(); @@ -857,7 +859,7 @@ export class BotEquipmentModGenerator ): IChooseRandomCompatibleModResult { let chosenTpl: string; - const exhaustableModPool = new ExhaustableArray(modPool, this.randomUtil, this.jsonUtil); + const exhaustableModPool = new ExhaustableArray(modPool, this.randomUtil, this.cloner); let chosenModResult: IChooseRandomCompatibleModResult = { incompatible: true, found: false, reason: "unknown" }; const modParentFilterList = parentSlot._props.filters[0].Filter; @@ -1091,7 +1093,7 @@ export class BotEquipmentModGenerator const allowedItems = parentSlot._props.filters[0].Filter; // Find mod item that fits slot from sorted mod array - const exhaustableModPool = new ExhaustableArray(allowedItems, this.randomUtil, this.jsonUtil); + const exhaustableModPool = new ExhaustableArray(allowedItems, this.randomUtil, this.cloner); let tmpModTpl = modTpl; while (exhaustableModPool.hasValues()) { @@ -1221,7 +1223,7 @@ export class BotEquipmentModGenerator botEquipBlacklist: EquipmentFilterDetails, ): string[] { - const modsFromDynamicPool = this.jsonUtil.clone( + const modsFromDynamicPool = this.cloner.clone( this.botEquipmentModPoolService.getCompatibleModsForWeaponSlot(parentItemId, modSlot), ); @@ -1303,7 +1305,7 @@ export class BotEquipmentModGenerator const camoraFirstSlot = "camora_000"; if (modSlot in itemModPool) { - exhaustableModPool = new ExhaustableArray(itemModPool[modSlot], this.randomUtil, this.jsonUtil); + exhaustableModPool = new ExhaustableArray(itemModPool[modSlot], this.randomUtil, this.cloner); } else if (camoraFirstSlot in itemModPool) { @@ -1311,7 +1313,7 @@ export class BotEquipmentModGenerator exhaustableModPool = new ExhaustableArray( this.mergeCamoraPoolsTogether(itemModPool), this.randomUtil, - this.jsonUtil, + this.cloner, ); } else diff --git a/project/src/generators/BotGenerator.ts b/project/src/generators/BotGenerator.ts index 95323c2c..1cb79753 100644 --- a/project/src/generators/BotGenerator.ts +++ b/project/src/generators/BotGenerator.ts @@ -29,6 +29,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { BotEquipmentFilterService } from "@spt-aki/services/BotEquipmentFilterService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -57,6 +58,7 @@ export class BotGenerator @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -109,7 +111,7 @@ export class BotGenerator bot.Info.Settings.BotDifficulty = botGenerationDetails.botDifficulty; // Get raw json data for bot (Cloned) - const botJsonTemplateClone = this.jsonUtil.clone( + const botJsonTemplateClone = this.cloner.clone( this.botHelper.getBotTemplate(botGenerationDetails.isPmc ? bot.Info.Side : botGenerationDetails.role), ); @@ -124,7 +126,7 @@ export class BotGenerator */ protected getCloneOfBotBase(): IBotBase { - return this.jsonUtil.clone(this.databaseServer.getTables().bots.base); + return this.cloner.clone(this.databaseServer.getTables().bots.base); } /** diff --git a/project/src/generators/BotLootGenerator.ts b/project/src/generators/BotLootGenerator.ts index c8a244e4..1c1413e6 100644 --- a/project/src/generators/BotLootGenerator.ts +++ b/project/src/generators/BotLootGenerator.ts @@ -23,6 +23,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { BotLootCacheService } from "@spt-aki/services/BotLootCacheService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -49,6 +50,7 @@ export class BotLootGenerator @inject("BotLootCacheService") protected botLootCacheService: BotLootCacheService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -459,7 +461,7 @@ export class BotLootGenerator // Check if all the chosen currency items fit into wallet const canAddToContainer = this.inventoryHelper.canPlaceItemsInContainer( - this.jsonUtil.clone(containerGrid), // MUST clone grid before passing in as function modifies grid + this.cloner.clone(containerGrid), // MUST clone grid before passing in as function modifies grid itemsToAdd, ); if (canAddToContainer) diff --git a/project/src/generators/BotWeaponGenerator.ts b/project/src/generators/BotWeaponGenerator.ts index d4b8a9af..63d11a81 100644 --- a/project/src/generators/BotWeaponGenerator.ts +++ b/project/src/generators/BotWeaponGenerator.ts @@ -23,6 +23,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { BotWeaponModLimitService } from "@spt-aki/services/BotWeaponModLimitService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { RepairService } from "@spt-aki/services/RepairService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -51,6 +52,7 @@ export class BotWeaponGenerator @inject("LocalisationService") protected localisationService: LocalisationService, @inject("RepairService") protected repairService: RepairService, @injectAll("InventoryMagGen") protected inventoryMagGenComponents: IInventoryMagGen[], + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.botConfig = this.configServer.getConfig(ConfigTypes.BOT); @@ -323,7 +325,7 @@ export class BotWeaponGenerator { if (presetObj._items[0]._tpl === weaponTpl) { - preset = this.jsonUtil.clone(presetObj); + preset = this.cloner.clone(presetObj); break; } } @@ -582,7 +584,7 @@ export class BotWeaponGenerator { const desiredCaliber = this.getWeaponCaliber(weaponTemplate); - const compatibleCartridges = this.jsonUtil.clone(ammo[desiredCaliber]); + const compatibleCartridges = this.cloner.clone(ammo[desiredCaliber]); if (!compatibleCartridges || compatibleCartridges?.length === 0) { this.logger.debug( diff --git a/project/src/generators/LocationGenerator.ts b/project/src/generators/LocationGenerator.ts index 0ca82c71..45809ac2 100644 --- a/project/src/generators/LocationGenerator.ts +++ b/project/src/generators/LocationGenerator.ts @@ -23,6 +23,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; import { ObjectId } from "@spt-aki/utils/ObjectId"; @@ -61,6 +62,7 @@ export class LocationGenerator @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.locationConfig = this.configServer.getConfig(ConfigTypes.LOCATION); @@ -84,7 +86,8 @@ export class LocationGenerator const db = this.databaseServer.getTables(); const mapData: ILocation = db.locations[locationId]; - const staticWeaponsOnMapClone = this.jsonUtil.clone(mapData.staticContainers.staticWeapons); + const staticWeaponsOnMapClone = this.cloner.clone(mapData.staticContainers.staticWeapons); + if (!staticWeaponsOnMapClone) { this.logger.error(`Unable to find static weapon data for map: ${locationBase.Name}`); @@ -93,7 +96,8 @@ export class LocationGenerator // Add mounted weapons to output loot result.push(...staticWeaponsOnMapClone ?? []); - const allStaticContainersOnMapClone = this.jsonUtil.clone(mapData.staticContainers.staticContainers); + const allStaticContainersOnMapClone = this.cloner.clone(mapData.staticContainers.staticContainers); + if (!allStaticContainersOnMapClone) { this.logger.error(`Unable to find static container data for map: ${locationBase.Name}`); @@ -101,7 +105,8 @@ export class LocationGenerator const staticRandomisableContainersOnMap = this.getRandomisableContainersOnMap(allStaticContainersOnMapClone); // Containers that MUST be added to map (quest containers etc) - const staticForcedOnMapClone = this.jsonUtil.clone(mapData.staticContainers.staticForced); + const staticForcedOnMapClone = this.cloner.clone(mapData.staticContainers.staticForced); + if (!staticForcedOnMapClone) { this.logger.error(`Unable to find forced static data for map: ${locationBase.Name}`); @@ -192,7 +197,7 @@ export class LocationGenerator // EDGE CASE: These are containers without a group and have a probability < 100% if (groupId === "") { - const containerIdsCopy = this.jsonUtil.clone(data.containerIdsWithProbability); + const containerIdsCopy = this.cloner.clone(data.containerIdsWithProbability); // Roll each containers probability, if it passes, it gets added data.containerIdsWithProbability = {}; for (const containerId in containerIdsCopy) @@ -305,7 +310,7 @@ export class LocationGenerator } // Create probability array with all possible container ids in this group and their relataive probability of spawning - const containerDistribution = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const containerDistribution = new ProbabilityObjectArray(this.mathUtil, this.cloner); for (const x of containerIds) { containerDistribution.push(new ProbabilityObject(x, containerData.containerIdsWithProbability[x])); @@ -395,7 +400,7 @@ export class LocationGenerator locationName: string, ): IStaticContainerData { - const containerClone = this.jsonUtil.clone(staticContainer); + const containerClone = this.cloner.clone(staticContainer); const containerTpl = containerClone.template.Items[0]._tpl; // Create new unique parent id to prevent any collisions @@ -509,7 +514,7 @@ export class LocationGenerator ): number { // Create probability array to calcualte the total count of lootable items inside container - const itemCountArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const itemCountArray = new ProbabilityObjectArray(this.mathUtil, this.cloner); const countDistribution = staticLootDist[containerTypeId]?.itemcountDistribution; if (!countDistribution) { @@ -546,7 +551,7 @@ export class LocationGenerator const seasonalEventActive = this.seasonalEventService.seasonalEventEnabled(); const seasonalItemTplBlacklist = this.seasonalEventService.getInactiveSeasonalEventItems(); - const itemDistribution = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const itemDistribution = new ProbabilityObjectArray(this.mathUtil, this.cloner); const itemContainerDistribution = staticLootDist[containerTypeId]?.itemDistribution; if (!itemContainerDistribution) @@ -617,7 +622,7 @@ export class LocationGenerator const guaranteedLoosePoints: Spawnpoint[] = []; const blacklistedSpawnpoints = this.locationConfig.looseLootBlacklist[locationName]; - const spawnpointArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const spawnpointArray = new ProbabilityObjectArray(this.mathUtil, this.cloner); for (const spawnpoint of allDynamicSpawnpoints) { @@ -699,7 +704,7 @@ export class LocationGenerator continue; } - const itemArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const itemArray = new ProbabilityObjectArray(this.mathUtil, this.cloner); for (const itemDist of spawnPoint.itemDistribution) { if ( @@ -769,7 +774,7 @@ export class LocationGenerator // Create probability array of all spawn positions for this spawn id const spawnpointArray = new ProbabilityObjectArray( this.mathUtil, - this.jsonUtil, + this.cloner, ); for (const si of items) { @@ -986,7 +991,7 @@ export class LocationGenerator else if (this.itemHelper.isOfBaseclass(chosenTpl, BaseClasses.WEAPON)) { let children: Item[] = []; - const defaultPreset = this.jsonUtil.clone(this.presetHelper.getDefaultPreset(chosenTpl)); + const defaultPreset = this.cloner.clone(this.presetHelper.getDefaultPreset(chosenTpl)); if (defaultPreset) { try diff --git a/project/src/generators/PlayerScavGenerator.ts b/project/src/generators/PlayerScavGenerator.ts index e6c4b4eb..13ff9278 100644 --- a/project/src/generators/PlayerScavGenerator.ts +++ b/project/src/generators/PlayerScavGenerator.ts @@ -22,6 +22,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer"; import { BotLootCacheService } from "@spt-aki/services/BotLootCacheService"; import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -47,6 +48,7 @@ export class PlayerScavGenerator @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotGenerator") protected botGenerator: BotGenerator, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.playerScavConfig = this.configServer.getConfig(ConfigTypes.PLAYERSCAV); @@ -61,8 +63,8 @@ export class PlayerScavGenerator { // get karma level from profile const profile = this.saveServer.getProfile(sessionID); - const pmcDataClone = this.jsonUtil.clone(profile.characters.pmc); - const existingScavDataClone = this.jsonUtil.clone(profile.characters.scav); + const pmcDataClone = this.cloner.clone(profile.characters.pmc); + const existingScavDataClone = this.cloner.clone(profile.characters.scav); const scavKarmaLevel = this.getScavKarmaLevel(pmcDataClone); @@ -219,7 +221,7 @@ export class PlayerScavGenerator protected constructBotBaseTemplate(botTypeForLoot: string): IBotType { const baseScavType = "assault"; - const assaultBase = this.jsonUtil.clone(this.botHelper.getBotTemplate(baseScavType)); + const assaultBase = this.cloner.clone(this.botHelper.getBotTemplate(baseScavType)); // Loot bot is same as base bot, return base with no modification if (botTypeForLoot === baseScavType) @@ -227,7 +229,7 @@ export class PlayerScavGenerator return assaultBase; } - const lootBase = this.jsonUtil.clone(this.botHelper.getBotTemplate(botTypeForLoot)); + const lootBase = this.cloner.clone(this.botHelper.getBotTemplate(botTypeForLoot)); assaultBase.inventory = lootBase.inventory; assaultBase.chances = lootBase.chances; assaultBase.generation = lootBase.generation; diff --git a/project/src/generators/RagfairOfferGenerator.ts b/project/src/generators/RagfairOfferGenerator.ts index bbbbeb7e..4f65e1e6 100644 --- a/project/src/generators/RagfairOfferGenerator.ts +++ b/project/src/generators/RagfairOfferGenerator.ts @@ -27,6 +27,7 @@ import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService"; import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -60,6 +61,7 @@ export class RagfairOfferGenerator @inject("FenceService") protected fenceService: FenceService, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); @@ -123,7 +125,7 @@ export class RagfairOfferGenerator offerRequirements.push(requirement); } - const itemsClone = this.jsonUtil.clone(items); + const itemsClone = this.cloner.clone(items); // Add cartridges to offers for ammo boxes if (this.itemHelper.isOfBaseclass(itemsClone[0]._tpl, BaseClasses.AMMO_BOX)) @@ -380,7 +382,7 @@ export class RagfairOfferGenerator for (let index = 0; index < offerCount; index++) { // Clone the item so we don't have shared references and generate new item IDs - const clonedAssort = this.jsonUtil.clone(assortItemWithChildren); + const clonedAssort = this.cloner.clone(assortItemWithChildren); this.itemHelper.reparentItemAndChildren(clonedAssort[0], clonedAssort); // Clear unnecessary properties diff --git a/project/src/generators/RepeatableQuestGenerator.ts b/project/src/generators/RepeatableQuestGenerator.ts index a40833a4..fc953298 100644 --- a/project/src/generators/RepeatableQuestGenerator.ts +++ b/project/src/generators/RepeatableQuestGenerator.ts @@ -20,6 +20,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; import { ObjectId } from "@spt-aki/utils/ObjectId"; @@ -43,6 +44,7 @@ export class RepeatableQuestGenerator @inject("RepeatableQuestRewardGenerator") protected repeatableQuestRewardGenerator: RepeatableQuestRewardGenerator, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -873,7 +875,7 @@ export class RepeatableQuestGenerator // @Incomplete: define Type for "type". protected generateRepeatableTemplate(type: string, traderId: string, side: string): IRepeatableQuest { - const questClone = this.jsonUtil.clone( + const questClone = this.cloner.clone( this.databaseServer.getTables().templates.repeatableQuests.templates[type], ); questClone._id = this.objectId.generate(); diff --git a/project/src/generators/RepeatableQuestRewardGenerator.ts b/project/src/generators/RepeatableQuestRewardGenerator.ts index 7d3247af..fcdf5de8 100644 --- a/project/src/generators/RepeatableQuestRewardGenerator.ts +++ b/project/src/generators/RepeatableQuestRewardGenerator.ts @@ -19,6 +19,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { SeasonalEventService } from "@spt-aki/services/SeasonalEventService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; import { ObjectId } from "@spt-aki/utils/ObjectId"; @@ -43,6 +44,7 @@ export class RepeatableQuestRewardGenerator @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -144,7 +146,7 @@ export class RepeatableQuestRewardGenerator const defaultPresetPool = new ExhaustableArray( Object.values(this.presetHelper.getDefaultWeaponPresets()), this.randomUtil, - this.jsonUtil, + this.cloner, ); let chosenPreset: IPreset; while (defaultPresetPool.hasValues()) @@ -156,7 +158,7 @@ export class RepeatableQuestRewardGenerator { this.logger.debug(` Added weapon ${tpls[0]} with price ${presetPrice}`); roublesBudget -= presetPrice; - chosenPreset = this.jsonUtil.clone(randomPreset); + chosenPreset = this.cloner.clone(randomPreset); break; } } diff --git a/project/src/helpers/BotDifficultyHelper.ts b/project/src/helpers/BotDifficultyHelper.ts index cd8202bf..ef979e50 100644 --- a/project/src/helpers/BotDifficultyHelper.ts +++ b/project/src/helpers/BotDifficultyHelper.ts @@ -7,6 +7,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -23,6 +24,7 @@ export class BotDifficultyHelper @inject("LocalisationService") protected localisationService: LocalisationService, @inject("BotHelper") protected botHelper: BotHelper, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.pmcConfig = this.configServer.getConfig(ConfigTypes.PMC); @@ -61,7 +63,7 @@ export class BotDifficultyHelper { // get fallback this.logger.warning(this.localisationService.getText("bot-unable_to_get_bot_fallback_to_assault", type)); - this.databaseServer.getTables().bots.types[type] = this.jsonUtil.clone( + this.databaseServer.getTables().bots.types[type] = this.cloner.clone( this.databaseServer.getTables().bots.types.assault, ); } @@ -75,12 +77,12 @@ export class BotDifficultyHelper difficulty: difficulty, }), ); - this.databaseServer.getTables().bots.types[type].difficulty[difficulty] = this.jsonUtil.clone( + this.databaseServer.getTables().bots.types[type].difficulty[difficulty] = this.cloner.clone( this.databaseServer.getTables().bots.types.assault.difficulty[difficulty], ); } - return this.jsonUtil.clone(difficultySettings); + return this.cloner.clone(difficultySettings); } /** @@ -97,7 +99,7 @@ export class BotDifficultyHelper difficultySetting = this.convertBotDifficultyDropdownToBotDifficulty(difficultySetting); - return this.jsonUtil.clone(this.databaseServer.getTables().bots.types[type].difficulty[difficultySetting]); + return this.cloner.clone(this.databaseServer.getTables().bots.types[type].difficulty[difficultySetting]); } /** diff --git a/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts b/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts index f1c33306..6178feee 100644 --- a/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts +++ b/project/src/helpers/Dialogue/Commando/SptCommands/GiveCommand/GiveSptCommand.ts @@ -14,6 +14,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocaleService } from "@spt-aki/services/LocaleService"; import { MailSendService } from "@spt-aki/services/MailSendService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -43,6 +44,7 @@ export class GiveSptCommand implements ISptCommand @inject("LocaleService") protected localeService: LocaleService, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, + @inject("RecursiveCloner") protected cloner: ICloner, ) { } @@ -229,7 +231,7 @@ export class GiveSptCommand implements ISptCommand } for (let i = 0; i < quantity; i++) { - let items = this.jsonUtil.clone(preset._items); + let items = this.cloner.clone(preset._items); items = this.itemHelper.replaceIDs(items); itemsToSend.push(...items); } diff --git a/project/src/helpers/HandbookHelper.ts b/project/src/helpers/HandbookHelper.ts index 3a00e510..3f656b2b 100644 --- a/project/src/helpers/HandbookHelper.ts +++ b/project/src/helpers/HandbookHelper.ts @@ -6,6 +6,7 @@ import { Money } from "@spt-aki/models/enums/Money"; import { IItemConfig } from "@spt-aki/models/spt/config/IItemConfig"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; class LookupItem @@ -43,6 +44,7 @@ export class HandbookHelper @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.itemConfig = this.configServer.getConfig(ConfigTypes.ITEM); @@ -74,7 +76,7 @@ export class HandbookHelper itemToUpdate.Price = this.itemConfig.handbookPriceOverride[itemTpl]; } - const handbookDbClone = this.jsonUtil.clone(this.databaseServer.getTables().templates.handbook); + const handbookDbClone = this.cloner.clone(this.databaseServer.getTables().templates.handbook); for (const handbookItem of handbookDbClone.Items) { this.handbookPriceCache.items.byId.set(handbookItem.Id, handbookItem.Price); diff --git a/project/src/helpers/HealthHelper.ts b/project/src/helpers/HealthHelper.ts index ae9ae7c1..09033c37 100644 --- a/project/src/helpers/HealthHelper.ts +++ b/project/src/helpers/HealthHelper.ts @@ -7,6 +7,7 @@ import { IHealthConfig } from "@spt-aki/models/spt/config/IHealthConfig"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { SaveServer } from "@spt-aki/servers/SaveServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -21,6 +22,7 @@ export class HealthHelper @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("SaveServer") protected saveServer: SaveServer, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.healthConfig = this.configServer.getConfig(ConfigTypes.HEALTH); @@ -115,7 +117,7 @@ export class HealthHelper this.saveEffects( pmcData, sessionID, - this.jsonUtil.clone(this.saveServer.getProfile(sessionID).vitality.effects), + this.cloner.clone(this.saveServer.getProfile(sessionID).vitality.effects), deleteExistingEffects, ); } diff --git a/project/src/helpers/HideoutHelper.ts b/project/src/helpers/HideoutHelper.ts index 7684ffa6..7914e966 100644 --- a/project/src/helpers/HideoutHelper.ts +++ b/project/src/helpers/HideoutHelper.ts @@ -23,6 +23,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { PlayerService } from "@spt-aki/services/PlayerService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -54,6 +55,7 @@ export class HideoutHelper @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ConfigServer") protected configServer: ConfigServer, @inject("JsonUtil") protected jsonUtil: JsonUtil, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.hideoutConfig = this.configServer.getConfig(ConfigTypes.HIDEOUT); @@ -106,7 +108,7 @@ export class HideoutHelper for (const tool of bodyAsSingle.tools) { - const toolItem = this.jsonUtil.clone(pmcData.Inventory.items.find(x => x._id === tool.id)); + const toolItem = this.cloner.clone(pmcData.Inventory.items.find(x => x._id === tool.id)); // Make sure we only return as many as we took this.itemHelper.addUpdObjectToItem(toolItem); diff --git a/project/src/helpers/InRaidHelper.ts b/project/src/helpers/InRaidHelper.ts index 353d07ac..afcb8e1c 100644 --- a/project/src/helpers/InRaidHelper.ts +++ b/project/src/helpers/InRaidHelper.ts @@ -19,6 +19,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { SaveServer } from "@spt-aki/servers/SaveServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { ProfileFixerService } from "@spt-aki/services/ProfileFixerService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -45,6 +46,7 @@ export class InRaidHelper @inject("ProfileFixerService") protected profileFixerService: ProfileFixerService, @inject("ConfigServer") protected configServer: ConfigServer, @inject("RandomUtil") protected randomUtil: RandomUtil, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.lostOnDeathConfig = this.configServer.getConfig(ConfigTypes.LOST_ON_DEATH); @@ -480,7 +482,7 @@ export class InRaidHelper public setInventory(sessionID: string, serverProfile: IPmcData, postRaidProfile: IPmcData): void { // Store insurance (as removeItem() removes insurance also) - const insured = this.jsonUtil.clone(serverProfile.InsuredItems); + const insured = this.cloner.clone(serverProfile.InsuredItems); // Remove possible equipped items from before the raid this.inventoryHelper.removeItem(serverProfile, serverProfile.Inventory.equipment, sessionID); diff --git a/project/src/helpers/InventoryHelper.ts b/project/src/helpers/InventoryHelper.ts index 9dfd3a9b..5e04384e 100644 --- a/project/src/helpers/InventoryHelper.ts +++ b/project/src/helpers/InventoryHelper.ts @@ -30,6 +30,7 @@ import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -65,6 +66,7 @@ export class InventoryHelper @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY); @@ -129,7 +131,7 @@ export class InventoryHelper output: IItemEventRouterResponse, ): void { - const itemWithModsToAddClone = this.jsonUtil.clone(request.itemWithModsToAdd); + const itemWithModsToAddClone = this.cloner.clone(request.itemWithModsToAdd); // Get stash layouts ready for use const stashFS2D = this.getStashSlotMap(pmcData, sessionId); @@ -245,7 +247,7 @@ export class InventoryHelper { const pmcData = this.profileHelper.getPmcProfile(sessionId); - const stashFS2D = this.jsonUtil.clone(this.getStashSlotMap(pmcData, sessionId)); + const stashFS2D = this.cloner.clone(this.getStashSlotMap(pmcData, sessionId)); for (const itemWithChildren of itemsWithChildren) { if (this.canPlaceItemInContainer(stashFS2D, itemWithChildren)) @@ -534,7 +536,7 @@ export class InventoryHelper // Keep splitting items into stacks until none left if (remainingCountOfItemToAdd > 0) { - const newChildItemToAdd = this.jsonUtil.clone(itemToAdd); + const newChildItemToAdd = this.cloner.clone(itemToAdd); if (remainingCountOfItemToAdd > itemDetails._props.StackMaxSize) { // Reduce total count of item purchased by stack size we're going to add to inventory diff --git a/project/src/helpers/ItemHelper.ts b/project/src/helpers/ItemHelper.ts index 9e00536f..b821d548 100644 --- a/project/src/helpers/ItemHelper.ts +++ b/project/src/helpers/ItemHelper.ts @@ -13,6 +13,7 @@ import { ItemBaseClassService } from "@spt-aki/services/ItemBaseClassService"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocaleService } from "@spt-aki/services/LocaleService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { CompareUtil } from "@spt-aki/utils/CompareUtil"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -47,6 +48,7 @@ export class ItemHelper @inject("LocalisationService") protected localisationService: LocalisationService, @inject("LocaleService") protected localeService: LocaleService, @inject("CompareUtil") protected compareUtil: CompareUtil, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -428,7 +430,7 @@ export class ItemHelper */ public getItems(): ITemplateItem[] { - return this.jsonUtil.clone(Object.values(this.databaseServer.getTables().templates.items)); + return this.cloner.clone(Object.values(this.databaseServer.getTables().templates.items)); } /** @@ -734,7 +736,7 @@ export class ItemHelper // return the item as is. if (remainingCount <= maxStackSize) { - rootAndChildren.push(this.jsonUtil.clone(itemToSplit)); + rootAndChildren.push(this.cloner.clone(itemToSplit)); return rootAndChildren; } @@ -742,7 +744,7 @@ export class ItemHelper while (remainingCount) { const amount = Math.min(remainingCount, maxStackSize); - const newStackClone = this.jsonUtil.clone(itemToSplit); + const newStackClone = this.cloner.clone(itemToSplit); newStackClone._id = this.hashUtil.generate(); newStackClone.upd.StackObjectsCount = amount; @@ -775,7 +777,7 @@ export class ItemHelper while (remainingCount) { const amount = Math.min(remainingCount, itemMaxStackSize); - const newItemClone = this.jsonUtil.clone(itemToSplit); + const newItemClone = this.cloner.clone(itemToSplit); newItemClone._id = this.hashUtil.generate(); newItemClone.upd.StackObjectsCount = amount; @@ -836,7 +838,7 @@ export class ItemHelper fastPanel = null, ): Item[] { - let items = this.jsonUtil.clone(originalItems); // Deep-clone the items to avoid mutation. + let items = this.cloner.clone(originalItems); // Deep-clone the items to avoid mutation. let serialisedInventory = this.jsonUtil.serialize(items); for (const item of items) diff --git a/project/src/helpers/PresetHelper.ts b/project/src/helpers/PresetHelper.ts index 19eef6aa..fbe85192 100644 --- a/project/src/helpers/PresetHelper.ts +++ b/project/src/helpers/PresetHelper.ts @@ -2,6 +2,7 @@ import { inject, injectable } from "tsyringe"; import { IPreset } from "@spt-aki/models/eft/common/IGlobals"; import { BaseClasses } from "@spt-aki/models/enums/BaseClasses"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { ItemHelper } from "./ItemHelper"; @@ -16,6 +17,7 @@ export class PresetHelper @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ItemHelper") protected itemHelper: ItemHelper, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -70,8 +72,10 @@ export class PresetHelper { if (!this.defaultEquipmentPresets) { - this.defaultEquipmentPresets = Object.values(this.databaseServer.getTables().globals.ItemPresets).filter(preset => preset._encyclopedia !== undefined && this.itemHelper.armorItemCanHoldMods(preset._encyclopedia)) - .reduce((acc, cur) => + this.defaultEquipmentPresets = Object.values(this.databaseServer.getTables().globals.ItemPresets) + .filter(preset => preset._encyclopedia !== undefined + && this.itemHelper.armorItemCanHoldMods(preset._encyclopedia), + ).reduce((acc, cur) => { acc[cur._id] = cur; return acc; @@ -104,12 +108,12 @@ export class PresetHelper public getPreset(id: string): IPreset { - return this.jsonUtil.clone(this.databaseServer.getTables().globals.ItemPresets[id]); + return this.cloner.clone(this.databaseServer.getTables().globals.ItemPresets[id]); } public getAllPresets(): IPreset[] { - return this.jsonUtil.clone(Object.values(this.databaseServer.getTables().globals.ItemPresets)); + return this.cloner.clone(Object.values(this.databaseServer.getTables().globals.ItemPresets)); } public getPresets(templateId: string): IPreset[] diff --git a/project/src/helpers/ProfileHelper.ts b/project/src/helpers/ProfileHelper.ts index 102519d6..ef6cc8b8 100644 --- a/project/src/helpers/ProfileHelper.ts +++ b/project/src/helpers/ProfileHelper.ts @@ -15,6 +15,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { SaveServer } from "@spt-aki/servers/SaveServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { ProfileSnapshotService } from "@spt-aki/services/ProfileSnapshotService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -37,6 +38,7 @@ export class ProfileHelper @inject("ProfileSnapshotService") protected profileSnapshotService: ProfileSnapshotService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY); @@ -120,8 +122,8 @@ export class ProfileHelper scavProfile: IPmcData, ): IPmcData[] { - const clonedPmc = this.jsonUtil.clone(pmcProfile); - const clonedScav = this.jsonUtil.clone(scavProfile); + const clonedPmc = this.cloner.clone(pmcProfile); + const clonedScav = this.cloner.clone(scavProfile); const profileSnapshot = this.profileSnapshotService.getProfileSnapshot(sessionId); clonedPmc.Info.Level = profileSnapshot.characters.pmc.Info.Level; diff --git a/project/src/helpers/QuestHelper.ts b/project/src/helpers/QuestHelper.ts index 05febcea..80544a2d 100644 --- a/project/src/helpers/QuestHelper.ts +++ b/project/src/helpers/QuestHelper.ts @@ -28,6 +28,7 @@ import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocaleService } from "@spt-aki/services/LocaleService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { MailSendService } from "@spt-aki/services/MailSendService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -56,6 +57,7 @@ export class QuestHelper @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("MailSendService") protected mailSendService: MailSendService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -320,13 +322,13 @@ export class QuestHelper for (const target of targets) { // This has all the original id relations since we reset the id to the original after the splitStack - const itemsClone = [this.jsonUtil.clone(target)]; + const itemsClone = [this.cloner.clone(target)]; // Here we generate a new id for the root item target._id = this.hashUtil.generate(); for (const mod of mods) { - itemsClone.push(this.jsonUtil.clone(mod)); + itemsClone.push(this.cloner.clone(mod)); } rewardItems = rewardItems.concat(this.itemHelper.reparentItemAndChildren(target, itemsClone)); @@ -691,7 +693,7 @@ export class QuestHelper */ public getQuestWithOnlyLevelRequirementStartCondition(quest: IQuest): IQuest { - const updatedQuest = this.jsonUtil.clone(quest); + const updatedQuest = this.cloner.clone(quest); updatedQuest.conditions.AvailableForStart = updatedQuest.conditions.AvailableForStart.filter(q => q.conditionType === "Level", ); diff --git a/project/src/helpers/RagfairHelper.ts b/project/src/helpers/RagfairHelper.ts index 1e443942..e59ab32f 100644 --- a/project/src/helpers/RagfairHelper.ts +++ b/project/src/helpers/RagfairHelper.ts @@ -13,6 +13,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { RagfairLinkedItemService } from "@spt-aki/services/RagfairLinkedItemService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() @@ -30,6 +31,7 @@ export class RagfairHelper @inject("RagfairLinkedItemService") protected ragfairLinkedItemService: RagfairLinkedItemService, @inject("UtilityHelper") protected utilityHelper: UtilityHelper, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); @@ -162,7 +164,7 @@ export class RagfairHelper { if (!rootItem) { - rootItem = this.jsonUtil.clone(item); + rootItem = this.cloner.clone(item); rootItem.upd.OriginalStackObjectsCount = rootItem.upd.StackObjectsCount; } else diff --git a/project/src/helpers/RagfairServerHelper.ts b/project/src/helpers/RagfairServerHelper.ts index 97600248..9588e0a1 100644 --- a/project/src/helpers/RagfairServerHelper.ts +++ b/project/src/helpers/RagfairServerHelper.ts @@ -19,6 +19,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer"; import { ItemFilterService } from "@spt-aki/services/ItemFilterService"; import { LocaleService } from "@spt-aki/services/LocaleService"; import { MailSendService } from "@spt-aki/services/MailSendService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -50,6 +51,7 @@ export class RagfairServerHelper @inject("MailSendService") protected mailSendService: MailSendService, @inject("ItemFilterService") protected itemFilterService: ItemFilterService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.ragfairConfig = this.configServer.getConfig(ConfigTypes.RAGFAIR); @@ -291,7 +293,7 @@ export class RagfairServerHelper */ public getPresetItems(item: Item): Item[] { - const preset = this.jsonUtil.clone(this.databaseServer.getTables().globals.ItemPresets[item._id]._items); + const preset = this.cloner.clone(this.databaseServer.getTables().globals.ItemPresets[item._id]._items); return this.itemHelper.reparentItemAndChildren(item, preset); } @@ -307,7 +309,7 @@ export class RagfairServerHelper { if (this.databaseServer.getTables().globals.ItemPresets[itemId]._items[0]._tpl === item._tpl) { - const presetItems = this.jsonUtil.clone( + const presetItems = this.cloner.clone( this.databaseServer.getTables().globals.ItemPresets[itemId]._items, ); presets.push(this.itemHelper.reparentItemAndChildren(item, presetItems)); diff --git a/project/src/helpers/RepairHelper.ts b/project/src/helpers/RepairHelper.ts index 55f3c9e5..2a6dcaa6 100644 --- a/project/src/helpers/RepairHelper.ts +++ b/project/src/helpers/RepairHelper.ts @@ -7,6 +7,7 @@ import { IRepairConfig } from "@spt-aki/models/spt/config/IRepairConfig"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -21,6 +22,7 @@ export class RepairHelper @inject("RandomUtil") protected randomUtil: RandomUtil, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.repairConfig = this.configServer.getConfig(ConfigTypes.REPAIR); @@ -48,9 +50,9 @@ export class RepairHelper { this.logger.debug(`Adding ${amountToRepair} to ${itemToRepairDetails._name} using kit: ${useRepairKit}`); - const itemMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability); - const itemCurrentDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.Durability); - const itemCurrentMaxDurability = this.jsonUtil.clone(itemToRepair.upd.Repairable.MaxDurability); + const itemMaxDurability = this.cloner.clone(itemToRepair.upd.Repairable.MaxDurability); + const itemCurrentDurability = this.cloner.clone(itemToRepair.upd.Repairable.Durability); + const itemCurrentMaxDurability = this.cloner.clone(itemToRepair.upd.Repairable.MaxDurability); let newCurrentDurability = itemCurrentDurability + amountToRepair; let newCurrentMaxDurability = itemCurrentMaxDurability + amountToRepair; diff --git a/project/src/helpers/RepeatableQuestHelper.ts b/project/src/helpers/RepeatableQuestHelper.ts index f951f47a..97d5d319 100644 --- a/project/src/helpers/RepeatableQuestHelper.ts +++ b/project/src/helpers/RepeatableQuestHelper.ts @@ -2,6 +2,7 @@ import { inject, injectable } from "tsyringe"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { IEliminationConfig, IQuestConfig, IRepeatableQuestConfig } from "@spt-aki/models/spt/config/IQuestConfig"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; import { ProbabilityObject, ProbabilityObjectArray } from "@spt-aki/utils/RandomUtil"; @@ -15,6 +16,7 @@ export class RepeatableQuestHelper @inject("MathUtil") protected mathUtil: MathUtil, @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.questConfig = this.configServer.getConfig(ConfigTypes.QUEST); @@ -38,8 +40,8 @@ export class RepeatableQuestHelper public probabilityObjectArray(configArrayInput: ProbabilityObject[]): ProbabilityObjectArray { - const configArray = this.jsonUtil.clone(configArrayInput); - const probabilityArray = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const configArray = this.cloner.clone(configArrayInput); + const probabilityArray = new ProbabilityObjectArray(this.mathUtil, this.cloner); for (const configObject of configArray) { probabilityArray.push( diff --git a/project/src/helpers/TradeHelper.ts b/project/src/helpers/TradeHelper.ts index 769d7fb3..a974b8b6 100644 --- a/project/src/helpers/TradeHelper.ts +++ b/project/src/helpers/TradeHelper.ts @@ -22,6 +22,7 @@ import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { PaymentService } from "@spt-aki/services/PaymentService"; import { TraderPurchasePersisterService } from "@spt-aki/services/TraderPurchasePersisterService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -47,6 +48,7 @@ export class TradeHelper @inject("TraderPurchasePersisterService") protected traderPurchasePersisterService: TraderPurchasePersisterService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); @@ -105,7 +107,7 @@ export class TradeHelper // Get raw offer from ragfair, clone to prevent altering offer itself const allOffers = this.ragfairServer.getOffers(); - const offerWithItemCloned = this.jsonUtil.clone(allOffers.find(x => x._id === buyRequestData.item_id)); + const offerWithItemCloned = this.cloner.clone(allOffers.find(x => x._id === buyRequestData.item_id)); offerItems = offerWithItemCloned.items; } else if (buyRequestData.tid === Traders.FENCE) @@ -196,7 +198,7 @@ export class TradeHelper const itemsToSendToPlayer: Item[][] = []; while (itemsToSendRemaining > 0) { - const offerClone = this.jsonUtil.clone(offerItems); + const offerClone = this.cloner.clone(offerItems); // Handle stackable items that have a max stack size limit const itemCountToSend = Math.min(itemMaxStackSize, itemsToSendRemaining); offerClone[0].upd.StackObjectsCount = itemCountToSend; diff --git a/project/src/helpers/TraderAssortHelper.ts b/project/src/helpers/TraderAssortHelper.ts index a070c5bd..4d62805a 100644 --- a/project/src/helpers/TraderAssortHelper.ts +++ b/project/src/helpers/TraderAssortHelper.ts @@ -17,6 +17,7 @@ import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { TraderAssortService } from "@spt-aki/services/TraderAssortService"; import { TraderPurchasePersisterService } from "@spt-aki/services/TraderPurchasePersisterService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -46,6 +47,7 @@ export class TraderAssortHelper @inject("TraderHelper") protected traderHelper: TraderHelper, @inject("FenceService") protected fenceService: FenceService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); @@ -68,7 +70,7 @@ export class TraderAssortHelper return this.getRagfairDataAsTraderAssort(); } - const traderClone = this.jsonUtil.clone(this.databaseServer.getTables().traders[traderId]); + const traderClone = this.cloner.clone(this.databaseServer.getTables().traders[traderId]); const pmcProfile = this.profileHelper.getPmcProfile(sessionId); if (traderId === Traders.FENCE) @@ -250,7 +252,7 @@ export class TraderAssortHelper */ protected getPristineTraderAssorts(traderId: string): Item[] { - return this.jsonUtil.clone(this.traderAssortService.getPristineTraderAssort(traderId).items); + return this.cloner.clone(this.traderAssortService.getPristineTraderAssort(traderId).items); } /** diff --git a/project/src/loaders/BundleLoader.ts b/project/src/loaders/BundleLoader.ts index ad640fc2..6b6f3d8d 100644 --- a/project/src/loaders/BundleLoader.ts +++ b/project/src/loaders/BundleLoader.ts @@ -2,6 +2,7 @@ import path from "node:path"; import { inject, injectable } from "tsyringe"; import { HttpServerHelper } from "@spt-aki/helpers/HttpServerHelper"; import { BundleHashCacheService } from "@spt-aki/services/cache/BundleHashCacheService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { VFS } from "@spt-aki/utils/VFS"; @@ -31,6 +32,7 @@ export class BundleLoader @inject("VFS") protected vfs: VFS, @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("BundleHashCacheService") protected bundleHashCacheService: BundleHashCacheService, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -51,7 +53,7 @@ export class BundleLoader public getBundle(key: string): BundleInfo { - return this.jsonUtil.clone(this.bundles[key]); + return this.cloner.clone(this.bundles[key]); } public addBundles(modpath: string): void diff --git a/project/src/models/spt/server/ExhaustableArray.ts b/project/src/models/spt/server/ExhaustableArray.ts index f8277234..f19ba1a1 100644 --- a/project/src/models/spt/server/ExhaustableArray.ts +++ b/project/src/models/spt/server/ExhaustableArray.ts @@ -1,3 +1,4 @@ +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -5,9 +6,9 @@ export class ExhaustableArray implements IExhaustableArray { private pool: T[]; - constructor(private itemPool: T[], private randomUtil: RandomUtil, private jsonUtil: JsonUtil) + constructor(private itemPool: T[], private randomUtil: RandomUtil, private cloner: ICloner) { - this.pool = this.jsonUtil.clone(itemPool); + this.pool = this.cloner.clone(itemPool); } public getRandomValue(): T @@ -18,7 +19,7 @@ export class ExhaustableArray implements IExhaustableArray } const index = this.randomUtil.getInt(0, this.pool.length - 1); - const toReturn = this.jsonUtil.clone(this.pool[index]); + const toReturn = this.cloner.clone(this.pool[index]); this.pool.splice(index, 1); return toReturn; } @@ -30,7 +31,7 @@ export class ExhaustableArray implements IExhaustableArray return null; } - const toReturn = this.jsonUtil.clone(this.pool[0]); + const toReturn = this.cloner.clone(this.pool[0]); this.pool.splice(0, 1); return toReturn; } diff --git a/project/src/routers/EventOutputHolder.ts b/project/src/routers/EventOutputHolder.ts index d21eb795..87018e4b 100644 --- a/project/src/routers/EventOutputHolder.ts +++ b/project/src/routers/EventOutputHolder.ts @@ -4,6 +4,7 @@ import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { IHideoutImprovement, Productive, TraderInfo } from "@spt-aki/models/eft/common/tables/IBotBase"; import { ProfileChange, TraderData } from "@spt-aki/models/eft/itemEvent/IItemEventRouterBase"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -17,6 +18,7 @@ export class EventOutputHolder @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("TimeUtil") protected timeUtil: TimeUtil, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -54,7 +56,7 @@ export class EventOutputHolder production: {}, improvements: {}, skills: { Common: [], Mastering: [], Points: 0 }, - health: this.jsonUtil.clone(pmcData.Health), + health: this.cloner.clone(pmcData.Health), traderRelations: {}, // changedHideoutStashes: {}, recipeUnlocked: {}, @@ -72,15 +74,15 @@ export class EventOutputHolder const profileChanges: ProfileChange = this.output.profileChanges[sessionId]; profileChanges.experience = pmcData.Info.Experience; - profileChanges.health = this.jsonUtil.clone(pmcData.Health); - profileChanges.skills.Common = this.jsonUtil.clone(pmcData.Skills.Common); // Always send skills for Item event route response - profileChanges.skills.Mastering = this.jsonUtil.clone(pmcData.Skills.Mastering); + profileChanges.health = this.cloner.clone(pmcData.Health); + profileChanges.skills.Common = this.cloner.clone(pmcData.Skills.Common); // Always send skills for Item event route response + profileChanges.skills.Mastering = this.cloner.clone(pmcData.Skills.Mastering); // Clone productions to ensure we preseve the profile jsons data profileChanges.production = this.getProductionsFromProfileAndFlagComplete( - this.jsonUtil.clone(pmcData.Hideout.Production), + this.cloner.clone(pmcData.Hideout.Production), ); - profileChanges.improvements = this.jsonUtil.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData)); + profileChanges.improvements = this.cloner.clone(this.getImprovementsFromProfileAndFlagComplete(pmcData)); profileChanges.traderRelations = this.constructTraderRelations(pmcData.TradersInfo); // Fixes container craft from water collector not resetting after collection + removed completed normal crafts diff --git a/project/src/routers/ItemEventRouter.ts b/project/src/routers/ItemEventRouter.ts index bb09afb8..c6a63927 100644 --- a/project/src/routers/ItemEventRouter.ts +++ b/project/src/routers/ItemEventRouter.ts @@ -6,6 +6,7 @@ import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEve import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() @@ -18,6 +19,7 @@ export class ItemEventRouter @injectAll("IERouters") protected itemEventRouters: ItemEventRouterDefinition[], @inject("LocalisationService") protected localisationService: LocalisationService, @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} @@ -54,7 +56,7 @@ export class ItemEventRouter this.eventOutputHolder.updateOutputProperties(sessionID); // Clone output before resetting the output object ready for use next time - const outputClone = this.jsonUtil.clone(output); + const outputClone = this.cloner.clone(output); this.eventOutputHolder.resetOutput(sessionID); return outputClone; diff --git a/project/src/services/BotLootCacheService.ts b/project/src/services/BotLootCacheService.ts index 175adbb3..0b1b3462 100644 --- a/project/src/services/BotLootCacheService.ts +++ b/project/src/services/BotLootCacheService.ts @@ -9,6 +9,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { RagfairPriceService } from "@spt-aki/services/RagfairPriceService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() @@ -24,6 +25,7 @@ export class BotLootCacheService @inject("PMCLootGenerator") protected pmcLootGenerator: PMCLootGenerator, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("RagfairPriceService") protected ragfairPriceService: RagfairPriceService, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.clearCache(); @@ -111,7 +113,7 @@ export class BotLootCacheService break; } - return this.jsonUtil.clone(result); + return this.cloner.clone(result); } /** @@ -136,9 +138,9 @@ export class BotLootCacheService if (isPmc) { // Replace lootPool from bot json with our own generated list for PMCs - lootPool.Backpack = this.jsonUtil.clone(this.pmcLootGenerator.generatePMCBackpackLootPool(botRole)); - lootPool.Pockets = this.jsonUtil.clone(this.pmcLootGenerator.generatePMCPocketLootPool(botRole)); - lootPool.TacticalVest = this.jsonUtil.clone(this.pmcLootGenerator.generatePMCVestLootPool(botRole)); + lootPool.Backpack = this.cloner.clone(this.pmcLootGenerator.generatePMCBackpackLootPool(botRole)); + lootPool.Pockets = this.cloner.clone(this.pmcLootGenerator.generatePMCPocketLootPool(botRole)); + lootPool.TacticalVest = this.cloner.clone(this.pmcLootGenerator.generatePMCVestLootPool(botRole)); } // Backpack/Pockets etc diff --git a/project/src/services/FenceService.ts b/project/src/services/FenceService.ts index e3d46214..2c136fe1 100644 --- a/project/src/services/FenceService.ts +++ b/project/src/services/FenceService.ts @@ -21,6 +21,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -69,6 +70,7 @@ export class FenceService @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.traderConfig = this.configServer.getConfig(ConfigTypes.TRADER); @@ -134,13 +136,13 @@ export class FenceService } // Clone assorts so we can adjust prices before sending to client - const assort = this.jsonUtil.clone(this.fenceAssort); + const assort = this.cloner.clone(this.fenceAssort); this.adjustAssortItemPricesByConfigMultiplier(assort, 1, this.traderConfig.fence.presetPriceMult); // merge normal fence assorts + discount assorts if player standing is large enough if (pmcProfile.TradersInfo[Traders.FENCE].standing >= 6) { - const discountAssort = this.jsonUtil.clone(this.fenceDiscountAssort); + const discountAssort = this.cloner.clone(this.fenceDiscountAssort); this.adjustAssortItemPricesByConfigMultiplier( discountAssort, this.traderConfig.fence.discountOptions.itemPriceMult, @@ -163,7 +165,7 @@ export class FenceService { // HUGE THANKS TO LACYWAY AND LEAVES FOR PROVIDING THIS SOLUTION FOR SPT TO IMPLEMENT!! // Copy the item and its children - let clonedItems = this.jsonUtil.clone(this.itemHelper.findAndReturnChildrenAsItems(items, mainItem._id)); + let clonedItems = this.cloner.clone(this.itemHelper.findAndReturnChildrenAsItems(items, mainItem._id)); const root = clonedItems[0]; const cost = this.getItemPrice(root._tpl, clonedItems); @@ -310,7 +312,7 @@ export class FenceService */ public getRawFenceAssorts(): ITraderAssort { - return this.mergeAssorts(this.jsonUtil.clone(this.fenceAssort), this.jsonUtil.clone(this.fenceDiscountAssort)); + return this.mergeAssorts(this.cloner.clone(this.fenceAssort), this.cloner.clone(this.fenceDiscountAssort)); } /** @@ -682,7 +684,7 @@ export class FenceService { const result: ICreateFenceAssortsResult = { sptItems: [], barter_scheme: {}, loyal_level_items: {} }; - const baseFenceAssortClone = this.jsonUtil.clone(this.databaseServer.getTables().traders[Traders.FENCE].assort); + const baseFenceAssortClone = this.cloner.clone(this.databaseServer.getTables().traders[Traders.FENCE].assort); const itemTypeLimitCounts = this.initItemLimitCounter(this.traderConfig.fence.itemTypeLimits); if (itemCounts.item > 0) @@ -743,7 +745,7 @@ export class FenceService continue; } - let desiredAssortItemAndChildrenClone = this.jsonUtil.clone( + let desiredAssortItemAndChildrenClone = this.cloner.clone( this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssortClone.items, chosenBaseAssortRoot._id), ); @@ -814,7 +816,7 @@ export class FenceService assorts.sptItems.push(desiredAssortItemAndChildrenClone); - assorts.barter_scheme[rootItemBeingAdded._id] = this.jsonUtil.clone( + assorts.barter_scheme[rootItemBeingAdded._id] = this.cloner.clone( baseFenceAssortClone.barter_scheme[chosenBaseAssortRoot._id], ); @@ -1004,7 +1006,7 @@ export class FenceService const rootItemDb = this.itemHelper.getItem(randomPresetRoot._tpl)[1]; - const presetWithChildrenClone = this.jsonUtil.clone( + const presetWithChildrenClone = this.cloner.clone( this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssort.items, randomPresetRoot._id), ); @@ -1060,7 +1062,7 @@ export class FenceService const randomPresetRoot = this.randomUtil.getArrayValue(equipmentPresetRootItems); const rootItemDb = this.itemHelper.getItem(randomPresetRoot._tpl)[1]; - const presetWithChildrenClone = this.jsonUtil.clone( + const presetWithChildrenClone = this.cloner.clone( this.itemHelper.findAndReturnChildrenAsItems(baseFenceAssort.items, randomPresetRoot._id), ); diff --git a/project/src/services/InsuranceService.ts b/project/src/services/InsuranceService.ts index 70882349..31cb8cea 100644 --- a/project/src/services/InsuranceService.ts +++ b/project/src/services/InsuranceService.ts @@ -12,7 +12,6 @@ import { ISaveProgressRequestData } from "@spt-aki/models/eft/inRaid/ISaveProgre import { BonusType } from "@spt-aki/models/enums/BonusType"; import { ConfigTypes } from "@spt-aki/models/enums/ConfigTypes"; import { MessageType } from "@spt-aki/models/enums/MessageType"; -import { Traders } from "@spt-aki/models/enums/Traders"; import { IInsuranceConfig } from "@spt-aki/models/spt/config/IInsuranceConfig"; import { ILostOnDeathConfig } from "@spt-aki/models/spt/config/ILostOnDeathConfig"; import { IInsuranceEquipmentPkg } from "@spt-aki/models/spt/services/IInsuranceEquipmentPkg"; @@ -23,6 +22,7 @@ import { SaveServer } from "@spt-aki/servers/SaveServer"; import { LocaleService } from "@spt-aki/services/LocaleService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; import { MailSendService } from "@spt-aki/services/MailSendService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { RandomUtil } from "@spt-aki/utils/RandomUtil"; @@ -52,6 +52,7 @@ export class InsuranceService @inject("LocaleService") protected localeService: LocaleService, @inject("MailSendService") protected mailSendService: MailSendService, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.insuranceConfig = this.configServer.getConfig(ConfigTypes.INSURANCE); diff --git a/project/src/services/ProfileFixerService.ts b/project/src/services/ProfileFixerService.ts index d7207c49..199b60a6 100644 --- a/project/src/services/ProfileFixerService.ts +++ b/project/src/services/ProfileFixerService.ts @@ -21,6 +21,7 @@ import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { ConfigServer } from "@spt-aki/servers/ConfigServer"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { TimeUtil } from "@spt-aki/utils/TimeUtil"; @@ -46,6 +47,7 @@ export class ProfileFixerService @inject("HashUtil") protected hashUtil: HashUtil, @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ConfigServer") protected configServer: ConfigServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.coreConfig = this.configServer.getConfig(ConfigTypes.CORE); @@ -1322,7 +1324,7 @@ export class ProfileFixerService if ("OverallCounters" in fullProfile.characters.pmc.Stats) { this.logger.debug("Migrating stats object into new structure"); - const statsCopy = this.jsonUtil.clone(fullProfile.characters.pmc.Stats); + const statsCopy = this.cloner.clone(fullProfile.characters.pmc.Stats); // Clear stats object fullProfile.characters.pmc.Stats = { Eft: null }; @@ -1400,7 +1402,7 @@ export class ProfileFixerService if ("Improvements" in pmcProfile.Hideout) { const improvements = pmcProfile.Hideout.Improvements as Record; - pmcProfile.Hideout.Improvement = this.jsonUtil.clone(improvements); + pmcProfile.Hideout.Improvement = this.cloner.clone(improvements); delete pmcProfile.Hideout.Improvements; this.logger.success("Successfully migrated hideout Improvements data to new location, deleted old data"); } diff --git a/project/src/services/ProfileSnapshotService.ts b/project/src/services/ProfileSnapshotService.ts index 6d2da464..4adf8355 100644 --- a/project/src/services/ProfileSnapshotService.ts +++ b/project/src/services/ProfileSnapshotService.ts @@ -1,5 +1,6 @@ import { inject, injectable } from "tsyringe"; import { IAkiProfile } from "@spt-aki/models/eft/profile/IAkiProfile"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() @@ -7,7 +8,7 @@ export class ProfileSnapshotService { protected storedProfileSnapshots: Record = {}; - constructor(@inject("JsonUtil") protected jsonUtil: JsonUtil) + constructor(@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("RecursiveCloner") protected cloner: ICloner) {} /** @@ -17,7 +18,7 @@ export class ProfileSnapshotService */ public storeProfileSnapshot(sessionID: string, profile: IAkiProfile): void { - this.storedProfileSnapshots[sessionID] = this.jsonUtil.clone(profile); + this.storedProfileSnapshots[sessionID] = this.cloner.clone(profile); } /** diff --git a/project/src/services/TraderServicesService.ts b/project/src/services/TraderServicesService.ts index a0932a76..e2fabe4b 100644 --- a/project/src/services/TraderServicesService.ts +++ b/project/src/services/TraderServicesService.ts @@ -4,6 +4,7 @@ import { QuestStatus } from "@spt-aki/models/enums/QuestStatus"; import { ITraderServiceModel } from "@spt-aki/models/spt/services/ITraderServiceModel"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @injectable() @@ -14,13 +15,14 @@ export class TraderServicesService @inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("WinstonLogger") protected logger: ILogger, @inject("DatabaseServer") protected databaseServer: DatabaseServer, + @inject("RecursiveCloner") protected cloner: ICloner, ) {} public getTraderServices(sessionId: string, traderId: string): ITraderServiceModel[] { const pmcData = this.profileHelper.getPmcProfile(sessionId); - let traderServices = this.jsonUtil.clone(this.databaseServer.getTables().traders[traderId]?.services); + let traderServices = this.cloner.clone(this.databaseServer.getTables().traders[traderId]?.services); if (!traderServices) { return []; diff --git a/project/src/services/mod/CustomItemService.ts b/project/src/services/mod/CustomItemService.ts index ec391545..dbbb70c5 100644 --- a/project/src/services/mod/CustomItemService.ts +++ b/project/src/services/mod/CustomItemService.ts @@ -12,6 +12,7 @@ import { IDatabaseTables } from "@spt-aki/models/spt/server/IDatabaseTables"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { ItemBaseClassService } from "@spt-aki/services/ItemBaseClassService"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; @@ -27,6 +28,7 @@ export class CustomItemService @inject("DatabaseServer") protected databaseServer: DatabaseServer, @inject("ItemHelper") protected itemHelper: ItemHelper, @inject("ItemBaseClassService") protected itemBaseClassService: ItemBaseClassService, + @inject("RecursiveCloner") protected cloner: ICloner, ) { this.tables = this.databaseServer.getTables(); @@ -61,7 +63,7 @@ export class CustomItemService } // Clone existing item - const itemClone = this.jsonUtil.clone(tables.templates.items[newItemDetails.itemTplToClone]); + const itemClone = this.cloner.clone(tables.templates.items[newItemDetails.itemTplToClone]); // Update id and parentId of item itemClone._id = newItemId; diff --git a/project/src/utils/JsonUtil.ts b/project/src/utils/JsonUtil.ts index eb0a6807..e82b431f 100644 --- a/project/src/utils/JsonUtil.ts +++ b/project/src/utils/JsonUtil.ts @@ -242,6 +242,7 @@ export class JsonUtil * Convert into string and back into object to clone object * @param objectToClone Item to clone * @returns Cloned parameter + * @deprecated Use ICloner implementations, such as RecursiveCloner or StructuredCloner */ public clone(objectToClone: T): T { diff --git a/project/src/utils/RandomUtil.ts b/project/src/utils/RandomUtil.ts index f0d38abf..67b1142f 100644 --- a/project/src/utils/RandomUtil.ts +++ b/project/src/utils/RandomUtil.ts @@ -1,5 +1,6 @@ import { inject, injectable } from "tsyringe"; import { ILogger } from "@spt-aki/models/spt/utils/ILogger"; +import { ICloner } from "@spt-aki/utils/cloners/ICloner"; import { JsonUtil } from "@spt-aki/utils/JsonUtil"; import { MathUtil } from "@spt-aki/utils/MathUtil"; @@ -20,7 +21,7 @@ import { MathUtil } from "@spt-aki/utils/MathUtil"; */ export class ProbabilityObjectArray extends Array> { - constructor(private mathUtil: MathUtil, private jsonUtil: JsonUtil, ...items: ProbabilityObject[]) + constructor(private mathUtil: MathUtil, private cloner: ICloner, ...items: ProbabilityObject[]) { super(); this.push(...items); @@ -30,7 +31,7 @@ export class ProbabilityObjectArray extends Array, index: number, array: ProbabilityObject[]) => any, ): ProbabilityObjectArray { - return new ProbabilityObjectArray(this.mathUtil, this.jsonUtil, ...super.filter(callbackfn)); + return new ProbabilityObjectArray(this.mathUtil, this.cloner, ...super.filter(callbackfn)); } /** @@ -52,8 +53,8 @@ export class ProbabilityObjectArray extends Array { - const clone = this.jsonUtil.clone(this); - const probabliltyObjects = new ProbabilityObjectArray(this.mathUtil, this.jsonUtil); + const clone = this.cloner.clone(this); + const probabliltyObjects = new ProbabilityObjectArray(this.mathUtil, this.cloner); for (const ci of clone) { probabliltyObjects.push(new ProbabilityObject(ci.key, ci.relativeProbability, ci.data)); @@ -204,7 +205,7 @@ export class ProbabilityObject @injectable() export class RandomUtil { - constructor(@inject("JsonUtil") protected jsonUtil: JsonUtil, @inject("WinstonLogger") protected logger: ILogger) + constructor(@inject("RecursiveCloner") protected cloner: ICloner, @inject("WinstonLogger") protected logger: ILogger) { } @@ -342,7 +343,7 @@ export class RandomUtil let list = originalList; if (!replacement) { - list = this.jsonUtil.clone(originalList); + list = this.cloner.clone(originalList); } const results = []; diff --git a/project/src/utils/cloners/ICloner.ts b/project/src/utils/cloners/ICloner.ts new file mode 100644 index 00000000..8c98799d --- /dev/null +++ b/project/src/utils/cloners/ICloner.ts @@ -0,0 +1,4 @@ +export interface ICloner +{ + clone(obj: T): T +} diff --git a/project/src/utils/cloners/JsonCloner.ts b/project/src/utils/cloners/JsonCloner.ts new file mode 100644 index 00000000..c727801a --- /dev/null +++ b/project/src/utils/cloners/JsonCloner.ts @@ -0,0 +1,11 @@ +import { injectable } from "tsyringe"; +import type { ICloner } from "@spt-aki/utils/cloners/ICloner"; + +@injectable() +export class JsonCloner implements ICloner +{ + public clone(obj: T): T + { + return JSON.parse(JSON.stringify(obj)); + } +} diff --git a/project/src/utils/cloners/RecursiveCloner.ts b/project/src/utils/cloners/RecursiveCloner.ts new file mode 100644 index 00000000..328f9a45 --- /dev/null +++ b/project/src/utils/cloners/RecursiveCloner.ts @@ -0,0 +1,45 @@ +import { injectable } from "tsyringe"; +import type { ICloner } from "@spt-aki/utils/cloners/ICloner"; + +@injectable() +export class RecursiveCloner implements ICloner +{ + private static primitives = new Set([ + "string", + "number", + "boolean", + "bigint", + "symbol", + "undefined", + "null", + ]); + + public clone(obj: T): T + { + const typeOfObj = typeof obj; + // no need to clone these types, they are primitives + if (RecursiveCloner.primitives.has(typeOfObj)) + { + return obj; + } + // clone the object types + if (typeOfObj === "object") + { + if (Array.isArray(obj)) + { + // biome-ignore lint/suspicious/noExplicitAny: used for clone + const objArr = obj as Array; + return objArr.map(v => this.clone(v)) as T; + } + + const newObj = {}; + for (const propOf1 in obj) + { + newObj[propOf1.toString()] = this.clone(obj[propOf1]); + } + return newObj as T; + } + + throw new Error(`Cant clone ${JSON.stringify(obj)}`); + } +} diff --git a/project/src/utils/cloners/StructuredCloner.ts b/project/src/utils/cloners/StructuredCloner.ts new file mode 100644 index 00000000..89daf567 --- /dev/null +++ b/project/src/utils/cloners/StructuredCloner.ts @@ -0,0 +1,11 @@ +import { injectable } from "tsyringe"; +import type { ICloner } from "@spt-aki/utils/cloners/ICloner"; + +@injectable() +export class StructuredCloner implements ICloner +{ + public clone(obj: T): T + { + return structuredClone(obj); + } +}