From 89fd1c95a7f69b178889bd470d9a2a9c251e8e0e Mon Sep 17 00:00:00 2001 From: Jesse Date: Tue, 11 Feb 2025 21:29:39 +0100 Subject: [PATCH] Wipe / Prestiging changes (#1106) - Full wipe character on unlocking prestige, this will force player to run through the player setup menu again (This now mirrors live) - Wiping a profile now keeps the achievements, prestige level and total time played (This now mirrors live) - Wiping or prestiging now re-unlocks achievements as intended - Stops `addAchievementToProfile` from adding with a new time if an achievement is already added. --- project/src/controllers/PrestigeController.ts | 182 +----------------- project/src/di/Container.ts | 4 +- project/src/helpers/PrestigeHelper.ts | 139 +++++++++++++ project/src/helpers/ProfileHelper.ts | 1 + project/src/helpers/RewardHelper.ts | 7 +- project/src/models/eft/profile/ISptProfile.ts | 7 + project/src/services/CreateProfileService.ts | 86 ++++++++- 7 files changed, 239 insertions(+), 187 deletions(-) create mode 100644 project/src/helpers/PrestigeHelper.ts diff --git a/project/src/controllers/PrestigeController.ts b/project/src/controllers/PrestigeController.ts index 3d4ba0ed..cf82b783 100644 --- a/project/src/controllers/PrestigeController.ts +++ b/project/src/controllers/PrestigeController.ts @@ -1,55 +1,17 @@ -import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator"; -import { DialogueHelper } from "@spt/helpers/DialogueHelper"; -import { InventoryHelper } from "@spt/helpers/InventoryHelper"; -import type { ItemHelper } from "@spt/helpers/ItemHelper"; import { ProfileHelper } from "@spt/helpers/ProfileHelper"; -import { QuestHelper } from "@spt/helpers/QuestHelper"; -import { TraderHelper } from "@spt/helpers/TraderHelper"; -import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage"; import { IPrestige } from "@spt/models/eft/common/tables/IPrestige"; -import { IReward } from "@spt/models/eft/common/tables/IReward"; -import { IAddItemDirectRequest } from "@spt/models/eft/inventory/IAddItemDirectRequest"; -import { IAddItemsDirectRequest } from "@spt/models/eft/inventory/IAddItemsDirectRequest"; import { IObtainPrestigeRequest } from "@spt/models/eft/prestige/IObtainPrestigeRequest"; -import { IProfileCreateRequestData } from "@spt/models/eft/profile/IProfileCreateRequestData"; -import { ISptProfile } from "@spt/models/eft/profile/ISptProfile"; -import { SkillTypes } from "@spt/models/enums/SkillTypes"; -import type { ILogger } from "@spt/models/spt/utils/ILogger"; -import { EventOutputHolder } from "@spt/routers/EventOutputHolder"; +import { IPendingPrestige } from "@spt/models/eft/profile/ISptProfile"; import { SaveServer } from "@spt/servers/SaveServer"; -import { CreateProfileService } from "@spt/services/CreateProfileService"; import { DatabaseService } from "@spt/services/DatabaseService"; -import { LocalisationService } from "@spt/services/LocalisationService"; -import { MailSendService } from "@spt/services/MailSendService"; -import { ProfileFixerService } from "@spt/services/ProfileFixerService"; -import { SeasonalEventService } from "@spt/services/SeasonalEventService"; -import { HashUtil } from "@spt/utils/HashUtil"; -import { TimeUtil } from "@spt/utils/TimeUtil"; -import type { ICloner } from "@spt/utils/cloners/ICloner"; import { inject, injectable } from "tsyringe"; import type { IEmptyRequestData } from "../models/eft/common/IEmptyRequestData"; @injectable() export class PrestigeController { constructor( - @inject("PrimaryLogger") protected logger: ILogger, - @inject("HashUtil") protected hashUtil: HashUtil, - @inject("PrimaryCloner") protected cloner: ICloner, - @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("SaveServer") protected saveServer: SaveServer, @inject("DatabaseService") protected databaseService: DatabaseService, - @inject("ItemHelper") protected itemHelper: ItemHelper, - @inject("ProfileFixerService") protected profileFixerService: ProfileFixerService, - @inject("LocalisationService") protected localisationService: LocalisationService, - @inject("CreateProfileService") protected createProfileService: CreateProfileService, - @inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService, - @inject("MailSendService") protected mailSendService: MailSendService, - @inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator, - @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, - @inject("TraderHelper") protected traderHelper: TraderHelper, - @inject("DialogueHelper") protected dialogueHelper: DialogueHelper, - @inject("InventoryHelper") protected inventoryHelper: InventoryHelper, - @inject("QuestHelper") protected questHelper: QuestHelper, @inject("ProfileHelper") protected profileHelper: ProfileHelper, ) {} @@ -65,14 +27,12 @@ export class PrestigeController { */ public async obtainPrestige(sessionId: string, request: IObtainPrestigeRequest[]): Promise { // Going to prestige 1 - // transfer // 5% of skills should be transfered over // 5% of mastering should be transfered over // earned achievements should be transfered over // profile stats should be transfered over // prestige progress should be transfered over - // reset // trader standing // task progress @@ -80,142 +40,18 @@ export class PrestigeController { // stash // hideout progress - const prePrestigeProfileClone = this.cloner.clone(this.profileHelper.getFullProfile(sessionId)); - const prePrestigePmc = prePrestigeProfileClone.characters.pmc; - const createRequest: IProfileCreateRequestData = { - side: prePrestigePmc.Info.Side, - nickname: prePrestigePmc.Info.Nickname, - headId: prePrestigePmc.Customization.Head, - voiceId: Object.values(this.databaseService.getTemplates().customization).find( - (customisation) => customisation._name === prePrestigePmc.Info.Voice, - )._id, - sptForcePrestigeLevel: prePrestigeProfileClone.characters.pmc.Info.PrestigeLevel + 1, // Current + 1, - }; + const profile = this.profileHelper.getFullProfile(sessionId); - // Reset profile - await this.createProfileService.createProfile(sessionId, createRequest); - - // Get freshly reset profile ready for editing - const newProfile = this.profileHelper.getFullProfile(sessionId); - if (!newProfile) { - this.logger.error(`Unable to create get new profile for: ${sessionId}`); - - return; - } - - // Skill copy - const commonSKillsToCopy = prePrestigePmc.Skills.Common; - for (const skillToCopy of commonSKillsToCopy) { - // Set progress 5% of what it was - skillToCopy.Progress = skillToCopy.Progress * 0.05; - const existingSkill = newProfile.characters.pmc.Skills.Common.find((skill) => skill.Id === skillToCopy.Id); - if (existingSkill) { - existingSkill.Progress = skillToCopy.Progress; - } else { - newProfile.characters.pmc.Skills.Common.push(skillToCopy); - } - } - - const masteringSkillsToCopy = prePrestigePmc.Skills.Mastering; - for (const skillToCopy of masteringSkillsToCopy) { - // Set progress 5% of what it was - skillToCopy.Progress = skillToCopy.Progress * 0.05; - const existingSkill = newProfile.characters.pmc.Skills.Mastering.find( - (skill) => skill.Id === skillToCopy.Id, - ); - if (existingSkill) { - existingSkill.Progress = skillToCopy.Progress; - } else { - newProfile.characters.pmc.Skills.Mastering.push(skillToCopy); - } - } - - const indexOfPrestigeObtained = Math.min(createRequest.sptForcePrestigeLevel - 1, 1); // Index starts at 0 - - // Add existing completed achievements and new one for prestige - newProfile.characters.pmc.Achievements = prePrestigeProfileClone.characters.pmc.Achievements; // this *should* only contain completed ones - - // Add "Prestigious" achievement - if (!newProfile.characters.pmc.Achievements["676091c0f457869a94017a23"]) { - newProfile.characters.pmc.Achievements["676091c0f457869a94017a23"] = this.timeUtil.getTimestamp(); - } - - // Assumes Prestige data is in descending order - const currentPrestigeData = this.databaseService.getTemplates().prestige.elements[indexOfPrestigeObtained]; - const prestigeRewards = this.databaseService - .getTemplates() - .prestige.elements.slice(0, indexOfPrestigeObtained + 1) - .flatMap((prestige) => prestige.rewards); - - this.addPrestigeRewardsToProfile(sessionId, newProfile, prestigeRewards); - - // Flag profile as having achieved this prestige level - newProfile.characters.pmc.Prestige[currentPrestigeData.id] = this.timeUtil.getTimestamp(); - - // Copy transferred items - for (const transferRequest of request) { - const item = prePrestigePmc.Inventory.items.find((item) => item._id === transferRequest.id); - if (!item) { - this.logger.error( - `Unable to find item with id: ${transferRequest.id} in profile: ${sessionId}, skipping`, - ); - - continue; - } - const addItemRequest: IAddItemDirectRequest = { - itemWithModsToAdd: [item], - foundInRaid: item.upd?.SpawnedInSession ?? false, - useSortingTable: false, + if (profile) { + const pendingPrestige: IPendingPrestige = { + prestigeLevel: profile.characters.pmc.Info.PrestigeLevel + 1, + items: request, }; - this.inventoryHelper.addItemToStash( - sessionId, - addItemRequest, - newProfile.characters.pmc, - this.eventOutputHolder.getOutput(sessionId), - ); - } - // Force save of above changes to disk - await this.saveServer.saveProfile(sessionId); - } + profile.spt.pendingPrestige = pendingPrestige; + profile.info.wipe = true; - protected addPrestigeRewardsToProfile(sessionId: string, newProfile: ISptProfile, rewards: IReward[]) { - for (const reward of rewards) { - switch (reward.type) { - case "CustomizationDirect": { - this.profileHelper.addHideoutCustomisationUnlock(newProfile, reward, CustomisationSource.PRESTIGE); - break; - } - case "Skill": - this.profileHelper.addSkillPointsToPlayer( - newProfile.characters.pmc, - reward.target as SkillTypes, - reward.value as number, - ); - break; - case "Item": { - const addItemRequest: IAddItemDirectRequest = { - itemWithModsToAdd: reward.items, - foundInRaid: reward.items[0]?.upd?.SpawnedInSession, - useSortingTable: false, - callback: null, - }; - this.inventoryHelper.addItemToStash( - sessionId, - addItemRequest, - newProfile.characters.pmc, - this.eventOutputHolder.getOutput(sessionId), - ); - break; - } - // case "ExtraDailyQuest": { - // // todo - // break; - // } - default: - this.logger.error(`Unhandled prestige reward type: ${reward.type}`); - break; - } + await this.saveServer.saveProfile(sessionId); } } } diff --git a/project/src/di/Container.ts b/project/src/di/Container.ts index a2c47ef4..f8f74362 100644 --- a/project/src/di/Container.ts +++ b/project/src/di/Container.ts @@ -109,6 +109,7 @@ import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper"; import { NotifierHelper } from "@spt/helpers/NotifierHelper"; import { PaymentHelper } from "@spt/helpers/PaymentHelper"; import { PresetHelper } from "@spt/helpers/PresetHelper"; +import { PrestigeHelper } from "@spt/helpers/PrestigeHelper"; import { ProbabilityHelper } from "@spt/helpers/ProbabilityHelper"; import { ProfileHelper } from "@spt/helpers/ProfileHelper"; import { QuestConditionHelper } from "@spt/helpers/QuestConditionHelper"; @@ -120,8 +121,8 @@ import { RagfairSellHelper } from "@spt/helpers/RagfairSellHelper"; import { RagfairServerHelper } from "@spt/helpers/RagfairServerHelper"; import { RagfairSortHelper } from "@spt/helpers/RagfairSortHelper"; import { RepairHelper } from "@spt/helpers/RepairHelper"; -import { RewardHelper } from "@spt/helpers/RewardHelper"; import { RepeatableQuestHelper } from "@spt/helpers/RepeatableQuestHelper"; +import { RewardHelper } from "@spt/helpers/RewardHelper"; import { SecureContainerHelper } from "@spt/helpers/SecureContainerHelper"; import { TradeHelper } from "@spt/helpers/TradeHelper"; import { TraderAssortHelper } from "@spt/helpers/TraderAssortHelper"; @@ -636,6 +637,7 @@ export class Container { }); depContainer.register("BotDifficultyHelper", { useClass: BotDifficultyHelper }); depContainer.register("RepeatableQuestHelper", { useClass: RepeatableQuestHelper }); + depContainer.register("PrestigeHelper", PrestigeHelper); // ChatBots depContainer.register("SptDialogueChatBot", SptDialogueChatBot); diff --git a/project/src/helpers/PrestigeHelper.ts b/project/src/helpers/PrestigeHelper.ts new file mode 100644 index 00000000..984b7c9d --- /dev/null +++ b/project/src/helpers/PrestigeHelper.ts @@ -0,0 +1,139 @@ +import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage"; +import { IItem } from "@spt/models/eft/common/tables/IItem"; +import { IReward } from "@spt/models/eft/common/tables/IReward"; +import { IPendingPrestige, ISptProfile } from "@spt/models/eft/profile/ISptProfile"; +import { SkillTypes } from "@spt/models/enums/SkillTypes"; +import type { ILogger } from "@spt/models/spt/utils/ILogger"; +import { DatabaseService } from "@spt/services/DatabaseService"; +import { MailSendService } from "@spt/services/MailSendService"; +import { TimeUtil } from "@spt/utils/TimeUtil"; +import type { ICloner } from "@spt/utils/cloners/ICloner"; +import { inject, injectable } from "tsyringe"; +import { ProfileHelper } from "./ProfileHelper"; +import { RewardHelper } from "./RewardHelper"; + +@injectable() +export class PrestigeHelper { + constructor( + @inject("PrimaryLogger") protected logger: ILogger, + @inject("PrimaryCloner") protected cloner: ICloner, + @inject("TimeUtil") protected timeUtil: TimeUtil, + @inject("DatabaseService") protected databaseService: DatabaseService, + @inject("MailSendService") protected mailSendService: MailSendService, + @inject("ProfileHelper") protected profileHelper: ProfileHelper, + @inject("RewardHelper") protected rewardHelper: RewardHelper, + ) {} + + public processPendingPrestige(oldProfile: ISptProfile, newProfile: ISptProfile, prestige: IPendingPrestige) { + const prePrestigePmc = oldProfile.characters.pmc; + const sessionId = newProfile.info.id; + + // Skill copy + + if (prePrestigePmc.Skills.Common) { + const commonSKillsToCopy = prePrestigePmc.Skills.Common; + for (const skillToCopy of commonSKillsToCopy) { + // Set progress 5% of what it was + skillToCopy.Progress = skillToCopy.Progress * 0.05; + const existingSkill = newProfile.characters.pmc.Skills.Common.find( + (skill) => skill.Id === skillToCopy.Id, + ); + if (existingSkill) { + existingSkill.Progress = skillToCopy.Progress; + } else { + newProfile.characters.pmc.Skills.Common.push(skillToCopy); + } + } + + const masteringSkillsToCopy = prePrestigePmc.Skills.Mastering; + for (const skillToCopy of masteringSkillsToCopy) { + // Set progress 5% of what it was + skillToCopy.Progress = skillToCopy.Progress * 0.05; + const existingSkill = newProfile.characters.pmc.Skills.Mastering.find( + (skill) => skill.Id === skillToCopy.Id, + ); + if (existingSkill) { + existingSkill.Progress = skillToCopy.Progress; + } else { + newProfile.characters.pmc.Skills.Mastering.push(skillToCopy); + } + } + } + + const indexOfPrestigeObtained = Math.min(prestige.prestigeLevel - 1, 1); // Index starts at 0 + + // Add "Prestigious" achievement + if (!newProfile.characters.pmc.Achievements["676091c0f457869a94017a23"]) { + this.rewardHelper.addAchievementToProfile(newProfile, "676091c0f457869a94017a23"); + } + + // Assumes Prestige data is in descending order + const currentPrestigeData = this.databaseService.getTemplates().prestige.elements[indexOfPrestigeObtained]; + const prestigeRewards = this.databaseService + .getTemplates() + .prestige.elements.slice(0, indexOfPrestigeObtained + 1) + .flatMap((prestige) => prestige.rewards); + + this.addPrestigeRewardsToProfile(sessionId, newProfile, prestigeRewards); + + // Flag profile as having achieved this prestige level + newProfile.characters.pmc.Prestige[currentPrestigeData.id] = this.timeUtil.getTimestamp(); + + const itemsToTransfer: IItem[] = []; + + // Copy transferred items + for (const transferRequest of prestige.items ?? []) { + const item = prePrestigePmc.Inventory.items.find((item) => item._id === transferRequest.id); + if (!item) { + this.logger.error( + `Unable to find item with id: ${transferRequest.id} in profile: ${sessionId}, skipping`, + ); + + continue; + } + + itemsToTransfer.push(item); + } + + this.mailSendService.sendSystemMessageToPlayer(sessionId, "", itemsToTransfer, 31536000); + + newProfile.characters.pmc.Info.PrestigeLevel = prestige.prestigeLevel; + } + + protected addPrestigeRewardsToProfile(sessionId: string, newProfile: ISptProfile, rewards: IReward[]) { + const itemsToSend: IItem[] = []; + + for (const reward of rewards) { + switch (reward.type) { + case "CustomizationDirect": { + this.profileHelper.addHideoutCustomisationUnlock(newProfile, reward, CustomisationSource.PRESTIGE); + break; + } + case "Skill": + this.profileHelper.addSkillPointsToPlayer( + newProfile.characters.pmc, + reward.target as SkillTypes, + reward.value as number, + ); + break; + case "Item": { + if (reward.items) { + itemsToSend.push(...reward.items); + } + break; + } + // case "ExtraDailyQuest": { + // // todo + // break; + // } + default: + this.logger.error(`Unhandled prestige reward type: ${reward.type}`); + break; + } + } + + if (itemsToSend.length > 0) { + this.mailSendService.sendSystemMessageToPlayer(sessionId, "", itemsToSend, 31536000); + } + } +} diff --git a/project/src/helpers/ProfileHelper.ts b/project/src/helpers/ProfileHelper.ts index 043709e6..726b8f4d 100644 --- a/project/src/helpers/ProfileHelper.ts +++ b/project/src/helpers/ProfileHelper.ts @@ -198,6 +198,7 @@ export class ProfileHelper { cultistRewards: new Map(), mods: [], receivedGifts: [], + pendingPrestige: undefined, }; } diff --git a/project/src/helpers/RewardHelper.ts b/project/src/helpers/RewardHelper.ts index 0726ee68..5c43d92f 100644 --- a/project/src/helpers/RewardHelper.ts +++ b/project/src/helpers/RewardHelper.ts @@ -9,7 +9,6 @@ import { IReward } from "@spt/models/eft/common/tables/IReward"; import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction"; import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse"; import { ISptProfile } from "@spt/models/eft/profile/ISptProfile"; -import { BaseClasses } from "@spt/models/enums/BaseClasses"; import { RewardType } from "@spt/models/enums/RewardType"; import { SkillTypes } from "@spt/models/enums/SkillTypes"; import type { ILogger } from "@spt/models/spt/utils/ILogger"; @@ -348,8 +347,10 @@ export class RewardHelper { * @param achievementId Id of achievement to add */ public addAchievementToProfile(fullProfile: ISptProfile, achievementId: string): void { - // Add achievement id to profile with timestamp it was unlocked - fullProfile.characters.pmc.Achievements[achievementId] = this.timeUtil.getTimestamp(); + if (!fullProfile.characters.pmc.Achievements[achievementId]) { + // Add achievement id to profile with timestamp it was unlocked + fullProfile.characters.pmc.Achievements[achievementId] = this.timeUtil.getTimestamp(); + } // Check for any customisation unlocks const achievementDataDb = this.databaseService diff --git a/project/src/models/eft/profile/ISptProfile.ts b/project/src/models/eft/profile/ISptProfile.ts index 39566e8e..f71ab63a 100644 --- a/project/src/models/eft/profile/ISptProfile.ts +++ b/project/src/models/eft/profile/ISptProfile.ts @@ -5,6 +5,7 @@ import { EquipmentBuildType } from "@spt/models/enums/EquipmentBuildType"; import { MemberCategory } from "@spt/models/enums/MemberCategory"; import { MessageType } from "@spt/models/enums/MessageType"; import { IProfileChangeEvent } from "@spt/models/spt/dialog/ISendMessageDetails"; +import { IObtainPrestigeRequest } from "../prestige/IObtainPrestigeRequest"; import { ISystemData } from "./ISystemData"; import { IUserDialogInfo } from "./IUserDialogInfo"; @@ -175,6 +176,12 @@ export interface ISpt { migrations?: Record; /** Cultist circle rewards received that are one time use, key (md5) is a combination of sacrificed + reward items */ cultistRewards?: Map; + pendingPrestige?: IPendingPrestige; +} + +export interface IPendingPrestige { + prestigeLevel: number; + items?: IObtainPrestigeRequest[]; } export interface IAcceptedCultistReward { diff --git a/project/src/services/CreateProfileService.ts b/project/src/services/CreateProfileService.ts index b1237ed9..438b068b 100644 --- a/project/src/services/CreateProfileService.ts +++ b/project/src/services/CreateProfileService.ts @@ -1,15 +1,18 @@ import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator"; import { ItemHelper } from "@spt/helpers/ItemHelper"; +import { PrestigeHelper } from "@spt/helpers/PrestigeHelper"; import { ProfileHelper } from "@spt/helpers/ProfileHelper"; import { QuestHelper } from "@spt/helpers/QuestHelper"; import { QuestRewardHelper } from "@spt/helpers/QuestRewardHelper"; +import { RewardHelper } from "@spt/helpers/RewardHelper"; import { TraderHelper } from "@spt/helpers/TraderHelper"; import { IPmcData } from "@spt/models/eft/common/IPmcData"; import { CustomisationSource, CustomisationType } from "@spt/models/eft/common/tables/ICustomisationStorage"; +import { IItem } from "@spt/models/eft/common/tables/IItem"; import { ITemplateSide } from "@spt/models/eft/common/tables/IProfileTemplate"; import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse"; import { IProfileCreateRequestData } from "@spt/models/eft/profile/IProfileCreateRequestData"; -import { IInraid, ISptProfile, IVitality } from "@spt/models/eft/profile/ISptProfile"; +import { IInraid, IPendingPrestige, ISptProfile, IVitality } from "@spt/models/eft/profile/ISptProfile"; import { GameEditions } from "@spt/models/enums/GameEditions"; import { ItemTpl } from "@spt/models/enums/ItemTpl"; import { MessageType } from "@spt/models/enums/MessageType"; @@ -41,16 +44,18 @@ export class CreateProfileService { @inject("TraderHelper") protected traderHelper: TraderHelper, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("MailSendService") protected mailSendService: MailSendService, + @inject("PrestigeHelper") protected prestigeHelper: PrestigeHelper, @inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator, @inject("QuestRewardHelper") protected questRewardHelper: QuestRewardHelper, + @inject("RewardHelper") protected rewardHelper: RewardHelper, @inject("PrimaryCloner") protected cloner: ICloner, @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, ) {} public async createProfile(sessionID: string, info: IProfileCreateRequestData): Promise { - const account = this.saveServer.getProfile(sessionID).info; + const account = await this.cloner.cloneAsync(this.saveServer.getProfile(sessionID)); const profileTemplateClone: ITemplateSide = await this.cloner.cloneAsync( - this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()], + this.databaseService.getProfiles()[account.info.edition][info.side.toLowerCase()], ); const pmcData = profileTemplateClone.character; @@ -58,12 +63,12 @@ export class CreateProfileService { this.deleteProfileBySessionId(sessionID); // PMC - pmcData._id = account.id; - pmcData.aid = account.aid; - pmcData.savage = account.scavId; + pmcData._id = account.info.id; + pmcData.aid = account.info.aid; + pmcData.savage = account.info.scavId; pmcData.sessionId = sessionID; pmcData.Info.Nickname = info.nickname; - pmcData.Info.LowerNickname = account.username.toLowerCase(); + pmcData.Info.LowerNickname = account.info.username.toLowerCase(); pmcData.Info.RegistrationDate = this.timeUtil.getTimestamp(); pmcData.Info.Voice = this.databaseService.getCustomization()[info.voiceId]._name; pmcData.Stats = this.profileHelper.getDefaultCounters(); @@ -77,8 +82,21 @@ export class CreateProfileService { pmcData.CoopExtractCounts = {}; pmcData.Achievements = {}; - if (typeof info.sptForcePrestigeLevel === "number") { - pmcData.Info.PrestigeLevel = info.sptForcePrestigeLevel; + // Process handling if the account has been forced to wipe + // BSG keeps both the achievements, prestige level and the total in-game time in a wipe. + if (account.characters.pmc.Achievements) { + pmcData.Achievements = account.characters.pmc.Achievements; + } + + if (account.characters.pmc.Prestige) { + pmcData.Prestige = account.characters.pmc.Prestige; + pmcData.Info.PrestigeLevel = account.characters.pmc.Info.PrestigeLevel; + } + + if (account.characters?.pmc?.Stats?.Eft) { + if (pmcData.Stats.Eft) { + pmcData.Stats.Eft.TotalInGameTime = account.characters.pmc.Stats.Eft.TotalInGameTime; + } } this.updateInventoryEquipmentId(pmcData); @@ -100,7 +118,7 @@ export class CreateProfileService { // Create profile const profileDetails: ISptProfile = { - info: account, + info: account.info, characters: { pmc: pmcData, scav: {} as IPmcData }, suits: profileTemplateClone.suits, userbuilds: profileTemplateClone.userbuilds, @@ -120,6 +138,54 @@ export class CreateProfileService { this.saveServer.addProfile(profileDetails); + if (Object.keys(profileDetails.characters.pmc.Achievements).length > 0) { + const achievementsDb = this.databaseService.getTemplates().achievements; + const achievementRewardItemsToSend: IItem[] = []; + + for (const achievementId in profileDetails.characters.pmc.Achievements) { + const rewards = achievementsDb.find((achievementDb) => achievementDb.id === achievementId)?.rewards; + + if (!rewards) { + continue; + } + + achievementRewardItemsToSend.push( + ...this.rewardHelper.applyRewards( + rewards, + CustomisationSource.ACHIEVEMENT, + profileDetails, + profileDetails.characters.pmc, + achievementId, + ), + ); + } + + if (achievementRewardItemsToSend.length > 0) { + this.mailSendService.sendLocalisedSystemMessageToPlayer( + profileDetails.info.id, + "670547bb5fa0b1a7c30d5836 0", + achievementRewardItemsToSend, + [], + 31536000, + ); + } + } + + // Process handling if the account is forced to prestige, or if the account currently has any pending prestiges + if (info.sptForcePrestigeLevel || account.spt?.pendingPrestige) { + let pendingPrestige: IPendingPrestige; + + if (account.spt.pendingPrestige) { + pendingPrestige = account.spt.pendingPrestige; + } else { + pendingPrestige = { + prestigeLevel: info.sptForcePrestigeLevel as number, + }; + } + + this.prestigeHelper.processPendingPrestige(account, profileDetails, pendingPrestige); + } + if (profileTemplateClone.trader.setQuestsAvailableForStart) { this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [QuestStatus.AvailableForStart]); }