Introduced a new ICloner interface with 3 implementations, one of them being a recursive cloner which is faster and more efficient than its counterparts by more than 50%

This commit is contained in:
clodan 2024-05-11 19:57:38 +01:00
parent ba1ac09b0b
commit 793d884293
55 changed files with 304 additions and 127 deletions

View File

@ -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<void>[] = [];
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(() =>

View File

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

View File

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

View File

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

View File

@ -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"),
);

View File

@ -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<string, number>(this.mathUtil, this.jsonUtil);
const attachmentsProbabilityArray = new ProbabilityObjectArray<string, number>(this.mathUtil, this.cloner);
for (const attachmentTpl of Object.keys(weightedAttachmentByPrice))
{
attachmentsProbabilityArray.push(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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", ModLoadOrder, { lifecycle: Lifecycle.Singleton });
depContainer.register<ModTypeCheck>("ModTypeCheck", ModTypeCheck, { lifecycle: Lifecycle.Singleton });
depContainer.register<CompareUtil>("CompareUtil", CompareUtil, { lifecycle: Lifecycle.Singleton });
depContainer.register<ICloner>("StructuredCloner", StructuredCloner, { lifecycle: Lifecycle.Singleton });
depContainer.register<ICloner>("JsonCloner", JsonCloner, { lifecycle: Lifecycle.Singleton });
depContainer.register<ICloner>("RecursiveCloner", RecursiveCloner, { lifecycle: Lifecycle.Singleton });
}
private static registerRouters(depContainer: DependencyContainer): void

View File

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

View File

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

View File

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

View File

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

View File

@ -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<string>(this.mathUtil, this.jsonUtil);
const containerDistribution = new ProbabilityObjectArray<string>(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<number>(this.mathUtil, this.jsonUtil);
const itemCountArray = new ProbabilityObjectArray<number>(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<string>(this.mathUtil, this.jsonUtil);
const itemDistribution = new ProbabilityObjectArray<string>(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<string, Spawnpoint>(this.mathUtil, this.jsonUtil);
const spawnpointArray = new ProbabilityObjectArray<string, Spawnpoint>(this.mathUtil, this.cloner);
for (const spawnpoint of allDynamicSpawnpoints)
{
@ -699,7 +704,7 @@ export class LocationGenerator
continue;
}
const itemArray = new ProbabilityObjectArray<string>(this.mathUtil, this.jsonUtil);
const itemArray = new ProbabilityObjectArray<string>(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<string, SpawnpointsForced>(
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

View File

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

View File

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

View File

@ -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<IRepeatableQuest>(
const questClone = this.cloner.clone<IRepeatableQuest>(
this.databaseServer.getTables().templates.repeatableQuests.templates[type],
);
questClone._id = this.objectId.generate();

View File

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

View File

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

View File

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

View File

@ -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<T, I>
@ -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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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[]

View File

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

View File

@ -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",
);

View File

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

View File

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

View File

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

View File

@ -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<K, V>(configArrayInput: ProbabilityObject<K, V>[]): ProbabilityObjectArray<K, V>
{
const configArray = this.jsonUtil.clone(configArrayInput);
const probabilityArray = new ProbabilityObjectArray<K, V>(this.mathUtil, this.jsonUtil);
const configArray = this.cloner.clone(configArrayInput);
const probabilityArray = new ProbabilityObjectArray<K, V>(this.mathUtil, this.cloner);
for (const configObject of configArray)
{
probabilityArray.push(

View File

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

View File

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

View File

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

View File

@ -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<T> implements IExhaustableArray<T>
{
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<T> implements IExhaustableArray<T>
}
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<T> implements IExhaustableArray<T>
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<string, IHideoutImprovement>;
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");
}

View File

@ -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<string, IAkiProfile> = {};
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);
}
/**

View File

@ -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 [];

View File

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

View File

@ -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<T>(objectToClone: T): T
{

View File

@ -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<K, V = undefined> extends Array<ProbabilityObject<K, V>>
{
constructor(private mathUtil: MathUtil, private jsonUtil: JsonUtil, ...items: ProbabilityObject<K, V>[])
constructor(private mathUtil: MathUtil, private cloner: ICloner, ...items: ProbabilityObject<K, V>[])
{
super();
this.push(...items);
@ -30,7 +31,7 @@ export class ProbabilityObjectArray<K, V = undefined> extends Array<ProbabilityO
callbackfn: (value: ProbabilityObject<K, V>, index: number, array: ProbabilityObject<K, V>[]) => any,
): ProbabilityObjectArray<K, V>
{
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<K, V = undefined> extends Array<ProbabilityO
*/
clone(): ProbabilityObjectArray<K, V>
{
const clone = this.jsonUtil.clone(this);
const probabliltyObjects = new ProbabilityObjectArray<K, V>(this.mathUtil, this.jsonUtil);
const clone = this.cloner.clone(this);
const probabliltyObjects = new ProbabilityObjectArray<K, V>(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<K, V = undefined>
@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 = [];

View File

@ -0,0 +1,4 @@
export interface ICloner
{
clone<T>(obj: T): T
}

View File

@ -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<T>(obj: T): T
{
return JSON.parse(JSON.stringify(obj));
}
}

View File

@ -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>([
"string",
"number",
"boolean",
"bigint",
"symbol",
"undefined",
"null",
]);
public clone<T>(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<any>;
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)}`);
}
}

View File

@ -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<T>(obj: T): T
{
return structuredClone(obj);
}
}