mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-13 09:50:43 -05:00
Initial implementation of Prestige system
Refactor of Create profile code into own service Updated `addHideoutCustomisationLock` to use enums for parameters + refactored logic Removed redundant `HideoutCustomizationGen` script
This commit is contained in:
parent
228be2e50f
commit
ae7fddb96e
@ -22,7 +22,7 @@ export class PrestigeCallbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Handle client/prestige/obtain */
|
/** Handle client/prestige/obtain */
|
||||||
public obtainPrestige(url: string, info: IObtainPrestigeRequest, sessionID: string): INullResponseData {
|
public obtainPrestige(url: string, info: IObtainPrestigeRequest[], sessionID: string): INullResponseData {
|
||||||
this.prestigeController.obtainPrestige(sessionID, info);
|
this.prestigeController.obtainPrestige(sessionID, info);
|
||||||
|
|
||||||
return this.httpResponse.nullResponse();
|
return this.httpResponse.nullResponse();
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
||||||
|
import { InventoryHelper } from "@spt/helpers/InventoryHelper";
|
||||||
import type { ItemHelper } from "@spt/helpers/ItemHelper";
|
import type { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
import { QuestHelper } from "@spt/helpers/QuestHelper";
|
import { QuestHelper } from "@spt/helpers/QuestHelper";
|
||||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
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 { IPrestige } from "@spt/models/eft/common/tables/IPrestige";
|
||||||
|
import { IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
||||||
|
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 { 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 type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||||
import { SaveServer } from "@spt/servers/SaveServer";
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import { CreateProfileService } from "@spt/services/CreateProfileService";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
import { LocalisationService } from "@spt/services/LocalisationService";
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
||||||
import { MailSendService } from "@spt/services/MailSendService";
|
import { MailSendService } from "@spt/services/MailSendService";
|
||||||
@ -32,12 +41,14 @@ export class PrestigeController {
|
|||||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||||
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
|
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("CreateProfileService") protected createProfileService: CreateProfileService,
|
||||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
||||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
||||||
|
@inject("InventoryHelper") protected inventoryHelper: InventoryHelper,
|
||||||
@inject("QuestHelper") protected questHelper: QuestHelper,
|
@inject("QuestHelper") protected questHelper: QuestHelper,
|
||||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
) {}
|
) {}
|
||||||
@ -52,12 +63,125 @@ export class PrestigeController {
|
|||||||
/**
|
/**
|
||||||
* Handle /client/prestige/obtain
|
* Handle /client/prestige/obtain
|
||||||
*/
|
*/
|
||||||
public obtainPrestige(sessionID: string, request: IObtainPrestigeRequest): void {
|
public obtainPrestige(sessionId: string, request: IObtainPrestigeRequest[]): void {
|
||||||
// TODO
|
// TODO
|
||||||
// Reset profile back to default from template
|
// DONE Reset profile back to default from template
|
||||||
// Take items passed in from request and add to inventory
|
// DONE Set prestige level in profile
|
||||||
// Set prestige level in profile
|
// DONE Copy skills
|
||||||
|
// DONE Take items passed in from request and add to inventory
|
||||||
// Update dogtags to prestige type
|
// Update dogtags to prestige type
|
||||||
// Iterate over prestige.json rewards and add to profile
|
// DONE Iterate over prestige.json rewards and add to profile
|
||||||
|
// DONE add achievement
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset profile
|
||||||
|
this.createProfileService.createProfile(sessionId, createRequest);
|
||||||
|
|
||||||
|
// Get freshly reset profile ready for editing
|
||||||
|
const newProfile = this.profileHelper.getFullProfile(sessionId);
|
||||||
|
|
||||||
|
// Skill copy
|
||||||
|
const commonSKillsToCopy = prePrestigePmc.Skills.Common;
|
||||||
|
for (const skillToCopy of commonSKillsToCopy) {
|
||||||
|
// Set progress to max level 20
|
||||||
|
skillToCopy.Progress = Math.min(skillToCopy.Progress, 2000);
|
||||||
|
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 to max level 20
|
||||||
|
skillToCopy.Progress = Math.min(skillToCopy.Progress, 2000);
|
||||||
|
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 indexToGet = Math.min(createRequest.sptForcePrestigeLevel - 1, 1); // Index starts at 0
|
||||||
|
const rewards = this.databaseService.getTemplates().prestige.elements[indexToGet].rewards;
|
||||||
|
this.addPrestigeRewardsToProfile(sessionId, newProfile, rewards);
|
||||||
|
|
||||||
|
// Copy transferred items
|
||||||
|
for (const transferRequest of request) {
|
||||||
|
const item = prePrestigePmc.Inventory.items.find((item) => item._id === transferRequest.id);
|
||||||
|
const addItemRequest: IAddItemDirectRequest = {
|
||||||
|
itemWithModsToAdd: [item],
|
||||||
|
foundInRaid: item.upd?.SpawnedInSession,
|
||||||
|
useSortingTable: false,
|
||||||
|
callback: null,
|
||||||
|
};
|
||||||
|
this.inventoryHelper.addItemToStash(
|
||||||
|
sessionId,
|
||||||
|
addItemRequest,
|
||||||
|
newProfile.characters.pmc,
|
||||||
|
this.eventOutputHolder.getOutput(sessionId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "Prestigious" achievement
|
||||||
|
if (!newProfile.achievements["676091c0f457869a94017a23"]) {
|
||||||
|
newProfile.achievements["676091c0f457869a94017a23"] = this.timeUtil.getTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addPrestigeRewardsToProfile(sessionId: string, newProfile: ISptProfile, rewards: IQuestReward[]) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||||
import { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
|
||||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
|
||||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
import { QuestHelper } from "@spt/helpers/QuestHelper";
|
|
||||||
import { QuestRewardHelper } from "@spt/helpers/QuestRewardHelper";
|
|
||||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
import { ITemplateSide } from "@spt/models/eft/common/tables/IProfileTemplate";
|
|
||||||
import { IItemEventRouterResponse } from "@spt/models/eft/itemEvent/IItemEventRouterResponse";
|
|
||||||
import { IMiniProfile } from "@spt/models/eft/launcher/IMiniProfile";
|
import { IMiniProfile } from "@spt/models/eft/launcher/IMiniProfile";
|
||||||
import { IGetProfileStatusResponseData } from "@spt/models/eft/profile/GetProfileStatusResponseData";
|
import { IGetProfileStatusResponseData } from "@spt/models/eft/profile/GetProfileStatusResponseData";
|
||||||
import { IGetOtherProfileRequest } from "@spt/models/eft/profile/IGetOtherProfileRequest";
|
import { IGetOtherProfileRequest } from "@spt/models/eft/profile/IGetOtherProfileRequest";
|
||||||
@ -18,22 +11,11 @@ import { IProfileChangeVoiceRequestData } from "@spt/models/eft/profile/IProfile
|
|||||||
import { IProfileCreateRequestData } from "@spt/models/eft/profile/IProfileCreateRequestData";
|
import { IProfileCreateRequestData } from "@spt/models/eft/profile/IProfileCreateRequestData";
|
||||||
import { ISearchFriendRequestData } from "@spt/models/eft/profile/ISearchFriendRequestData";
|
import { ISearchFriendRequestData } from "@spt/models/eft/profile/ISearchFriendRequestData";
|
||||||
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
|
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
|
||||||
import { IInraid, ISptProfile, IVitality } from "@spt/models/eft/profile/ISptProfile";
|
|
||||||
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
|
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
|
||||||
import { GameEditions } from "@spt/models/enums/GameEditions";
|
|
||||||
import { ItemTpl } from "@spt/models/enums/ItemTpl";
|
|
||||||
import { MessageType } from "@spt/models/enums/MessageType";
|
|
||||||
import { QuestStatus } from "@spt/models/enums/QuestStatus";
|
|
||||||
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
|
||||||
import { SaveServer } from "@spt/servers/SaveServer";
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
import { CreateProfileService } from "@spt/services/CreateProfileService";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
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 type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
import { inject, injectable } from "tsyringe";
|
import { inject, injectable } from "tsyringe";
|
||||||
|
|
||||||
@ -41,22 +23,11 @@ import { inject, injectable } from "tsyringe";
|
|||||||
export class ProfileController {
|
export class ProfileController {
|
||||||
constructor(
|
constructor(
|
||||||
@inject("PrimaryLogger") protected logger: ILogger,
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
@inject("HashUtil") protected hashUtil: HashUtil,
|
|
||||||
@inject("PrimaryCloner") protected cloner: ICloner,
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
|
||||||
@inject("SaveServer") protected saveServer: SaveServer,
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
@inject("DatabaseService") protected databaseService: DatabaseService,
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
@inject("CreateProfileService") protected createProfileService: CreateProfileService,
|
||||||
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
|
|
||||||
@inject("LocalisationService") protected localisationService: LocalisationService,
|
|
||||||
@inject("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
|
||||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
|
||||||
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
||||||
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
|
||||||
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
|
||||||
@inject("DialogueHelper") protected dialogueHelper: DialogueHelper,
|
|
||||||
@inject("QuestHelper") protected questHelper: QuestHelper,
|
|
||||||
@inject("QuestRewardHelper") protected questRewardHelper: QuestRewardHelper,
|
|
||||||
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -129,311 +100,7 @@ export class ProfileController {
|
|||||||
* @returns Profiles _id value
|
* @returns Profiles _id value
|
||||||
*/
|
*/
|
||||||
public createProfile(info: IProfileCreateRequestData, sessionID: string): string {
|
public createProfile(info: IProfileCreateRequestData, sessionID: string): string {
|
||||||
const account = this.saveServer.getProfile(sessionID).info;
|
return this.createProfileService.createProfile(sessionID, info);
|
||||||
const profileTemplateClone: ITemplateSide = this.cloner.clone(
|
|
||||||
this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()],
|
|
||||||
);
|
|
||||||
const pmcData = profileTemplateClone.character;
|
|
||||||
|
|
||||||
// Delete existing profile
|
|
||||||
this.deleteProfileBySessionId(sessionID);
|
|
||||||
|
|
||||||
// PMC
|
|
||||||
pmcData._id = account.id;
|
|
||||||
pmcData.aid = account.aid;
|
|
||||||
pmcData.savage = account.scavId;
|
|
||||||
pmcData.sessionId = sessionID;
|
|
||||||
pmcData.Info.Nickname = info.nickname;
|
|
||||||
pmcData.Info.LowerNickname = account.username.toLowerCase();
|
|
||||||
pmcData.Info.RegistrationDate = this.timeUtil.getTimestamp();
|
|
||||||
pmcData.Info.Voice = this.databaseService.getCustomization()[info.voiceId]._name;
|
|
||||||
pmcData.Stats = this.profileHelper.getDefaultCounters();
|
|
||||||
pmcData.Info.NeedWipeOptions = [];
|
|
||||||
pmcData.Customization.Head = info.headId;
|
|
||||||
pmcData.Health.UpdateTime = this.timeUtil.getTimestamp();
|
|
||||||
pmcData.Quests = [];
|
|
||||||
pmcData.Hideout.Seed = this.timeUtil.getTimestamp() + 8 * 60 * 60 * 24 * 365; // 8 years in future why? who knows, we saw it in live
|
|
||||||
pmcData.RepeatableQuests = [];
|
|
||||||
pmcData.CarExtractCounts = {};
|
|
||||||
pmcData.CoopExtractCounts = {};
|
|
||||||
pmcData.Achievements = {};
|
|
||||||
|
|
||||||
this.updateInventoryEquipmentId(pmcData);
|
|
||||||
|
|
||||||
if (!pmcData.UnlockedInfo) {
|
|
||||||
pmcData.UnlockedInfo = { unlockedProductionRecipe: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add required items to pmc stash
|
|
||||||
this.addMissingInternalContainersToProfile(pmcData);
|
|
||||||
|
|
||||||
// Change item IDs to be unique
|
|
||||||
pmcData.Inventory.items = this.itemHelper.replaceIDs(
|
|
||||||
pmcData.Inventory.items,
|
|
||||||
pmcData,
|
|
||||||
undefined,
|
|
||||||
pmcData.Inventory.fastPanel,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create profile
|
|
||||||
const profileDetails: ISptProfile = {
|
|
||||||
info: account,
|
|
||||||
characters: { pmc: pmcData, scav: {} as IPmcData },
|
|
||||||
suits: profileTemplateClone.suits,
|
|
||||||
userbuilds: profileTemplateClone.userbuilds,
|
|
||||||
dialogues: profileTemplateClone.dialogues,
|
|
||||||
spt: this.profileHelper.getDefaultSptDataObject(),
|
|
||||||
vitality: {} as IVitality,
|
|
||||||
inraid: {} as IInraid,
|
|
||||||
insurance: [],
|
|
||||||
traderPurchases: {},
|
|
||||||
achievements: {},
|
|
||||||
friends: [],
|
|
||||||
customisationUnlocks: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addCustomisationUnlocksToProfile(profileDetails);
|
|
||||||
|
|
||||||
this.profileFixerService.checkForAndFixPmcProfileIssues(profileDetails.characters.pmc);
|
|
||||||
|
|
||||||
this.saveServer.addProfile(profileDetails);
|
|
||||||
|
|
||||||
if (profileTemplateClone.trader.setQuestsAvailableForStart) {
|
|
||||||
this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [QuestStatus.AvailableForStart]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Profile is flagged as wanting quests set to ready to hand in and collect rewards
|
|
||||||
if (profileTemplateClone.trader.setQuestsAvailableForFinish) {
|
|
||||||
this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [
|
|
||||||
QuestStatus.AvailableForStart,
|
|
||||||
QuestStatus.Started,
|
|
||||||
QuestStatus.AvailableForFinish,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Make unused response so applyQuestReward works
|
|
||||||
const response = this.eventOutputHolder.getOutput(sessionID);
|
|
||||||
|
|
||||||
// Add rewards for starting quests to profile
|
|
||||||
this.givePlayerStartingQuestRewards(profileDetails, sessionID, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetAllTradersInProfile(sessionID);
|
|
||||||
|
|
||||||
this.saveServer.getProfile(sessionID).characters.scav = this.generatePlayerScav(sessionID);
|
|
||||||
|
|
||||||
// Store minimal profile and reload it
|
|
||||||
this.saveServer.saveProfile(sessionID);
|
|
||||||
this.saveServer.loadProfile(sessionID);
|
|
||||||
|
|
||||||
// Completed account creation
|
|
||||||
this.saveServer.getProfile(sessionID).info.wipe = false;
|
|
||||||
this.saveServer.saveProfile(sessionID);
|
|
||||||
|
|
||||||
return pmcData._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addCustomisationUnlocksToProfile(fullProfile: ISptProfile) {
|
|
||||||
// Some game versions have additional dogtag variants, add them
|
|
||||||
switch (this.getGameEdition(fullProfile)) {
|
|
||||||
case GameEditions.EDGE_OF_DARKNESS:
|
|
||||||
// Gets EoD tags
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "6746fd09bafff85008048838",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "67471938bafff850080488b7",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
case GameEditions.UNHEARD:
|
|
||||||
// Gets EoD+Unheard tags
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "6746fd09bafff85008048838",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "67471938bafff850080488b7",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "67471928d17d6431550563b5",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "6747193f170146228c0d2226",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pretigeLevel = fullProfile?.characters?.pmc?.Info?.PrestigeLevel;
|
|
||||||
if (pretigeLevel) {
|
|
||||||
if (pretigeLevel >= 1) {
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "674dbf593bee1152d407f005",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pretigeLevel >= 2) {
|
|
||||||
fullProfile.customisationUnlocks.push({
|
|
||||||
id: "675dcfea7ae1a8792107ca99",
|
|
||||||
source: "default",
|
|
||||||
type: "dogTag",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getGameEdition(profile: ISptProfile): string {
|
|
||||||
const edition = profile.characters?.pmc?.Info?.GameVersion;
|
|
||||||
if (!edition) {
|
|
||||||
// Edge case - profile not created yet, fall back to what launcher has set
|
|
||||||
const launcherEdition = profile.info.edition;
|
|
||||||
switch (launcherEdition.toLowerCase()) {
|
|
||||||
case "edge of darkness":
|
|
||||||
return GameEditions.EDGE_OF_DARKNESS;
|
|
||||||
case "unheard":
|
|
||||||
return GameEditions.UNHEARD;
|
|
||||||
default:
|
|
||||||
return GameEditions.STANDARD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return edition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* make profiles pmcData.Inventory.equipment unique
|
|
||||||
* @param pmcData Profile to update
|
|
||||||
*/
|
|
||||||
protected updateInventoryEquipmentId(pmcData: IPmcData): void {
|
|
||||||
const oldEquipmentId = pmcData.Inventory.equipment;
|
|
||||||
pmcData.Inventory.equipment = this.hashUtil.generate();
|
|
||||||
|
|
||||||
for (const item of pmcData.Inventory.items) {
|
|
||||||
if (item.parentId === oldEquipmentId) {
|
|
||||||
item.parentId = pmcData.Inventory.equipment;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item._id === oldEquipmentId) {
|
|
||||||
item._id = pmcData.Inventory.equipment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure a profile has the necessary internal containers e.g. questRaidItems / sortingTable
|
|
||||||
* DOES NOT check that stash exists
|
|
||||||
* @param pmcData Profile to check
|
|
||||||
*/
|
|
||||||
protected addMissingInternalContainersToProfile(pmcData: IPmcData): void {
|
|
||||||
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.hideoutCustomizationStashId)) {
|
|
||||||
pmcData.Inventory.items.push({
|
|
||||||
_id: pmcData.Inventory.hideoutCustomizationStashId,
|
|
||||||
_tpl: ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.sortingTable)) {
|
|
||||||
pmcData.Inventory.items.push({
|
|
||||||
_id: pmcData.Inventory.sortingTable,
|
|
||||||
_tpl: ItemTpl.SORTINGTABLE_SORTING_TABLE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.questStashItems)) {
|
|
||||||
pmcData.Inventory.items.push({
|
|
||||||
_id: pmcData.Inventory.questStashItems,
|
|
||||||
_tpl: ItemTpl.STASH_QUESTOFFLINE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.questRaidItems)) {
|
|
||||||
pmcData.Inventory.items.push({
|
|
||||||
_id: pmcData.Inventory.questRaidItems,
|
|
||||||
_tpl: ItemTpl.STASH_QUESTRAID,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a profile
|
|
||||||
* @param sessionID Id of profile to delete
|
|
||||||
*/
|
|
||||||
protected deleteProfileBySessionId(sessionID: string): void {
|
|
||||||
if (sessionID in this.saveServer.getProfiles()) {
|
|
||||||
this.saveServer.deleteProfileById(sessionID);
|
|
||||||
} else {
|
|
||||||
this.logger.warning(
|
|
||||||
this.localisationService.getText("profile-unable_to_find_profile_by_id_cannot_delete", sessionID),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Iterate over all quests in player profile, inspect rewards for the quests current state (accepted/completed)
|
|
||||||
* and send rewards to them in mail
|
|
||||||
* @param profileDetails Player profile
|
|
||||||
* @param sessionID Session id
|
|
||||||
* @param response Event router response
|
|
||||||
*/
|
|
||||||
protected givePlayerStartingQuestRewards(
|
|
||||||
profileDetails: ISptProfile,
|
|
||||||
sessionID: string,
|
|
||||||
response: IItemEventRouterResponse,
|
|
||||||
): void {
|
|
||||||
for (const quest of profileDetails.characters.pmc.Quests) {
|
|
||||||
const questFromDb = this.questHelper.getQuestFromDb(quest.qid, profileDetails.characters.pmc);
|
|
||||||
|
|
||||||
// Get messageId of text to send to player as text message in game
|
|
||||||
// Copy of code from QuestController.acceptQuest()
|
|
||||||
const messageId = this.questHelper.getMessageIdForQuestStart(
|
|
||||||
questFromDb.startedMessageText,
|
|
||||||
questFromDb.description,
|
|
||||||
);
|
|
||||||
const itemRewards = this.questRewardHelper.applyQuestReward(
|
|
||||||
profileDetails.characters.pmc,
|
|
||||||
quest.qid,
|
|
||||||
QuestStatus.Started,
|
|
||||||
sessionID,
|
|
||||||
response,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
|
||||||
sessionID,
|
|
||||||
this.traderHelper.getTraderById(questFromDb.traderId),
|
|
||||||
MessageType.QUEST_START,
|
|
||||||
messageId,
|
|
||||||
itemRewards,
|
|
||||||
this.timeUtil.getHoursAsSeconds(100),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For each trader reset their state to what a level 1 player would see
|
|
||||||
* @param sessionId Session id of profile to reset
|
|
||||||
*/
|
|
||||||
protected resetAllTradersInProfile(sessionId: string): void {
|
|
||||||
for (const traderId in this.databaseService.getTraders()) {
|
|
||||||
this.traderHelper.resetTrader(sessionId, traderId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -209,6 +209,7 @@ import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
|||||||
import { BotNameService } from "@spt/services/BotNameService";
|
import { BotNameService } from "@spt/services/BotNameService";
|
||||||
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService";
|
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService";
|
||||||
import { CircleOfCultistService } from "@spt/services/CircleOfCultistService";
|
import { CircleOfCultistService } from "@spt/services/CircleOfCultistService";
|
||||||
|
import { CreateProfileService } from "@spt/services/CreateProfileService";
|
||||||
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
||||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||||
import { FenceService } from "@spt/services/FenceService";
|
import { FenceService } from "@spt/services/FenceService";
|
||||||
@ -820,6 +821,9 @@ export class Container {
|
|||||||
depContainer.register<PostDbLoadService>("PostDbLoadService", PostDbLoadService, {
|
depContainer.register<PostDbLoadService>("PostDbLoadService", PostDbLoadService, {
|
||||||
lifecycle: Lifecycle.Singleton,
|
lifecycle: Lifecycle.Singleton,
|
||||||
});
|
});
|
||||||
|
depContainer.register<CreateProfileService>("CreateProfileService", CreateProfileService, {
|
||||||
|
lifecycle: Lifecycle.Singleton,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static registerServers(depContainer: DependencyContainer): void {
|
private static registerServers(depContainer: DependencyContainer): void {
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
import { BanType, Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
|
import { BanType, Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
|
||||||
import { ICustomisationStorage } from "@spt/models/eft/common/tables/ICustomisationStorage";
|
import {
|
||||||
|
CustomisationSource,
|
||||||
|
CustomisationType,
|
||||||
|
ICustomisationStorage,
|
||||||
|
} from "@spt/models/eft/common/tables/ICustomisationStorage";
|
||||||
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
||||||
import { IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
import { IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
||||||
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
|
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
|
||||||
@ -582,8 +586,8 @@ export class ProfileHelper {
|
|||||||
// Reward found, add to profile
|
// Reward found, add to profile
|
||||||
fullProfile.customisationUnlocks.push({
|
fullProfile.customisationUnlocks.push({
|
||||||
id: customizationDirectReward.target,
|
id: customizationDirectReward.target,
|
||||||
source: "achievement",
|
source: CustomisationSource.ACHIEVEMENT,
|
||||||
type: customisationDataDb.type,
|
type: customisationDataDb.type as CustomisationType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,33 +655,75 @@ export class ProfileHelper {
|
|||||||
* @param reward reward given to player with customisation data
|
* @param reward reward given to player with customisation data
|
||||||
* @param source Source of reward, e.g. "unlockedInGame" for quests and "achievement" for achievements
|
* @param source Source of reward, e.g. "unlockedInGame" for quests and "achievement" for achievements
|
||||||
*/
|
*/
|
||||||
public addHideoutCustomisationUnlock(fullProfile: ISptProfile, reward: IQuestReward, source: string): void {
|
public addHideoutCustomisationUnlock(
|
||||||
// Get matching db data for reward
|
fullProfile: ISptProfile,
|
||||||
const hideoutCustomisationDb = this.databaseService
|
reward: IQuestReward,
|
||||||
.getHideout()
|
source: CustomisationSource,
|
||||||
.customisation.globals.find((customisation) => customisation.itemId === reward.target);
|
): void {
|
||||||
if (!hideoutCustomisationDb) {
|
|
||||||
this.logger.warning(
|
|
||||||
`Unable to add hideout customisaiton reward: ${reward.target} to profile: ${fullProfile.info.id} as matching object cannot be found in hideout/customisation.json`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fullProfile.customisationUnlocks ||= [];
|
fullProfile.customisationUnlocks ||= [];
|
||||||
if (fullProfile.customisationUnlocks?.some((unlock) => unlock.id === hideoutCustomisationDb.id)) {
|
if (fullProfile.customisationUnlocks?.some((unlock) => unlock.id === reward.target)) {
|
||||||
this.logger.warning(
|
this.logger.warning(
|
||||||
`Profile: ${fullProfile.info.id} already has hideout customisaiton reward: ${reward.target}, skipping`,
|
`Profile: ${fullProfile.info.id} already has hideout customisaiton reward: ${reward.target}, skipping`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const customisationTemplateDb = this.databaseService.getTemplates().customization;
|
||||||
|
const matchingCustomisation = customisationTemplateDb[reward.target];
|
||||||
|
|
||||||
|
if (matchingCustomisation) {
|
||||||
const rewardToStore: ICustomisationStorage = {
|
const rewardToStore: ICustomisationStorage = {
|
||||||
id: hideoutCustomisationDb.itemId,
|
id: reward.target,
|
||||||
source: source,
|
source: source,
|
||||||
type: hideoutCustomisationDb.type,
|
type: null,
|
||||||
};
|
};
|
||||||
|
switch (matchingCustomisation._parent) {
|
||||||
|
case "675ff48ce8d2356707079617": {
|
||||||
|
// MannequinPose
|
||||||
|
rewardToStore.type = CustomisationType.MANNEQUIN_POSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "6751848eba5968fd800a01d6": {
|
||||||
|
// Gestures
|
||||||
|
rewardToStore.type = CustomisationType.GESTURE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "67373f170eca6e03ab0d5391": {
|
||||||
|
// Floor
|
||||||
|
rewardToStore.type = CustomisationType.FLOOR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "6746fafabafff8500804880e": {
|
||||||
|
// DogTags
|
||||||
|
rewardToStore.type = CustomisationType.DOG_TAG;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "673b3f595bf6b605c90fcdc2": {
|
||||||
|
// Ceiling
|
||||||
|
rewardToStore.type = CustomisationType.CEILING;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "67373f1e5a5ee73f2a081baf": {
|
||||||
|
// Wall
|
||||||
|
rewardToStore.type = CustomisationType.WALL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
this.logger.error(
|
||||||
|
`Unhandled customisation unlock type: ${matchingCustomisation._parent} not added to profile`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fullProfile.customisationUnlocks.push(rewardToStore);
|
fullProfile.customisationUnlocks.push(rewardToStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const rewardToStore: ICustomisationStorage = {
|
||||||
|
// id: matchingHideoutCustomisation.itemId,
|
||||||
|
// source: source,
|
||||||
|
// type: matchingHideoutCustomisation.type as CustomisationType,
|
||||||
|
// };
|
||||||
|
|
||||||
|
// fullProfile.customisationUnlocks.push(rewardToStore);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { PresetHelper } from "@spt/helpers/PresetHelper";
|
|||||||
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
import { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
|
import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage";
|
||||||
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
||||||
import { IQuest, IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
import { IQuest, IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
||||||
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
|
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
|
||||||
@ -131,7 +132,11 @@ export class QuestRewardHelper {
|
|||||||
this.profileHelper.replaceProfilePocketTpl(pmcProfile, reward.target);
|
this.profileHelper.replaceProfilePocketTpl(pmcProfile, reward.target);
|
||||||
break;
|
break;
|
||||||
case QuestRewardType.CUSTOMIZATION_DIRECT:
|
case QuestRewardType.CUSTOMIZATION_DIRECT:
|
||||||
this.profileHelper.addHideoutCustomisationUnlock(fullProfile, reward, "unlockedInGame");
|
this.profileHelper.addHideoutCustomisationUnlock(
|
||||||
|
fullProfile,
|
||||||
|
reward,
|
||||||
|
CustomisationSource.UNLOCKED_IN_GAME,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
|
@ -1,5 +1,31 @@
|
|||||||
export interface ICustomisationStorage {
|
export interface ICustomisationStorage {
|
||||||
id: string; // Customiastion.json/itemId
|
id: string; // Customiastion.json/itemId
|
||||||
source: string;
|
source: CustomisationSource;
|
||||||
type: string;
|
type: CustomisationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CustomisationType {
|
||||||
|
SUITE = "suite",
|
||||||
|
DOG_TAG = "dogTag",
|
||||||
|
HEAD = "head",
|
||||||
|
VOICE = "voice",
|
||||||
|
GESTURE = "gesture",
|
||||||
|
ENVIRONMENT = "environment",
|
||||||
|
WALL = "wall",
|
||||||
|
FLOOR = "floor",
|
||||||
|
CEILING = "ceiling",
|
||||||
|
LIGHT = "light",
|
||||||
|
SHOOTING_RANGE_MARK = "shootingRangeMark",
|
||||||
|
CAT = "cat",
|
||||||
|
MANNEQUIN_POSE = "mannequinPose",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CustomisationSource {
|
||||||
|
QUEST = "quest",
|
||||||
|
PRESTIGE = "prestige",
|
||||||
|
ACHIEVEMENT = "achievement",
|
||||||
|
UNLOCKED_IN_GAME = "unlockedInGame",
|
||||||
|
PAID = "paid",
|
||||||
|
DROP = "drop",
|
||||||
|
DEFAULT = "default",
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { IQuestCondition, IQuestReward } from "./IQuest";
|
import type { IQuestCondition, IQuestReward } from "./IQuest";
|
||||||
|
|
||||||
export interface IPrestige {
|
export interface IPrestige {
|
||||||
elements: IPretigeElement;
|
elements: IPretigeElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPretigeElement {
|
export interface IPretigeElement {
|
||||||
|
@ -3,4 +3,5 @@ export interface IProfileCreateRequestData {
|
|||||||
nickname: string;
|
nickname: string;
|
||||||
headId: string;
|
headId: string;
|
||||||
voiceId: string;
|
voiceId: string;
|
||||||
|
sptForcePrestigeLevel?: number;
|
||||||
}
|
}
|
||||||
|
@ -12,4 +12,6 @@ export enum QuestRewardType {
|
|||||||
ACHIEVEMENT = "Achievement",
|
ACHIEVEMENT = "Achievement",
|
||||||
POCKETS = "Pockets",
|
POCKETS = "Pockets",
|
||||||
CUSTOMIZATION_DIRECT = "CustomizationDirect",
|
CUSTOMIZATION_DIRECT = "CustomizationDirect",
|
||||||
|
CUSTOMIZATION_OFFER = "CustomizationOffer",
|
||||||
|
EXTRA_DAILY_QUEST = "ExtraDailyQuest",
|
||||||
}
|
}
|
||||||
|
361
project/src/services/CreateProfileService.ts
Normal file
361
project/src/services/CreateProfileService.ts
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
import { PlayerScavGenerator } from "@spt/generators/PlayerScavGenerator";
|
||||||
|
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||||
|
import { ProfileHelper } from "@spt/helpers/ProfileHelper";
|
||||||
|
import { QuestHelper } from "@spt/helpers/QuestHelper";
|
||||||
|
import { QuestRewardHelper } from "@spt/helpers/QuestRewardHelper";
|
||||||
|
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 { 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 { GameEditions } from "@spt/models/enums/GameEditions";
|
||||||
|
import { ItemTpl } from "@spt/models/enums/ItemTpl";
|
||||||
|
import { MessageType } from "@spt/models/enums/MessageType";
|
||||||
|
import { QuestStatus } from "@spt/models/enums/QuestStatus";
|
||||||
|
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
||||||
|
import { EventOutputHolder } from "@spt/routers/EventOutputHolder";
|
||||||
|
import { SaveServer } from "@spt/servers/SaveServer";
|
||||||
|
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 { HashUtil } from "@spt/utils/HashUtil";
|
||||||
|
import { TimeUtil } from "@spt/utils/TimeUtil";
|
||||||
|
import type { ICloner } from "@spt/utils/cloners/ICloner";
|
||||||
|
import { inject, injectable } from "tsyringe";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class CreateProfileService {
|
||||||
|
constructor(
|
||||||
|
@inject("PrimaryLogger") protected logger: ILogger,
|
||||||
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
||||||
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
||||||
|
@inject("SaveServer") protected saveServer: SaveServer,
|
||||||
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
||||||
|
@inject("ProfileFixerService") protected profileFixerService: ProfileFixerService,
|
||||||
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
||||||
|
@inject("QuestHelper") protected questHelper: QuestHelper,
|
||||||
|
@inject("ProfileHelper") protected profileHelper: ProfileHelper,
|
||||||
|
@inject("TraderHelper") protected traderHelper: TraderHelper,
|
||||||
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
||||||
|
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||||
|
@inject("PlayerScavGenerator") protected playerScavGenerator: PlayerScavGenerator,
|
||||||
|
@inject("QuestRewardHelper") protected questRewardHelper: QuestRewardHelper,
|
||||||
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
||||||
|
@inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public createProfile(sessionID: string, info: IProfileCreateRequestData): string {
|
||||||
|
const account = this.saveServer.getProfile(sessionID).info;
|
||||||
|
const profileTemplateClone: ITemplateSide = this.cloner.clone(
|
||||||
|
this.databaseService.getProfiles()[account.edition][info.side.toLowerCase()],
|
||||||
|
);
|
||||||
|
const pmcData = profileTemplateClone.character;
|
||||||
|
|
||||||
|
// Delete existing profile
|
||||||
|
this.deleteProfileBySessionId(sessionID);
|
||||||
|
|
||||||
|
// PMC
|
||||||
|
pmcData._id = account.id;
|
||||||
|
pmcData.aid = account.aid;
|
||||||
|
pmcData.savage = account.scavId;
|
||||||
|
pmcData.sessionId = sessionID;
|
||||||
|
pmcData.Info.Nickname = info.nickname;
|
||||||
|
pmcData.Info.LowerNickname = account.username.toLowerCase();
|
||||||
|
pmcData.Info.RegistrationDate = this.timeUtil.getTimestamp();
|
||||||
|
pmcData.Info.Voice = this.databaseService.getCustomization()[info.voiceId]._name;
|
||||||
|
pmcData.Stats = this.profileHelper.getDefaultCounters();
|
||||||
|
pmcData.Info.NeedWipeOptions = [];
|
||||||
|
pmcData.Customization.Head = info.headId;
|
||||||
|
pmcData.Health.UpdateTime = this.timeUtil.getTimestamp();
|
||||||
|
pmcData.Quests = [];
|
||||||
|
pmcData.Hideout.Seed = this.timeUtil.getTimestamp() + 8 * 60 * 60 * 24 * 365; // 8 years in future why? who knows, we saw it in live
|
||||||
|
pmcData.RepeatableQuests = [];
|
||||||
|
pmcData.CarExtractCounts = {};
|
||||||
|
pmcData.CoopExtractCounts = {};
|
||||||
|
pmcData.Achievements = {};
|
||||||
|
|
||||||
|
if (typeof info.sptForcePrestigeLevel === "number") {
|
||||||
|
pmcData.Info.PrestigeLevel = info.sptForcePrestigeLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateInventoryEquipmentId(pmcData);
|
||||||
|
|
||||||
|
if (!pmcData.UnlockedInfo) {
|
||||||
|
pmcData.UnlockedInfo = { unlockedProductionRecipe: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add required items to pmc stash
|
||||||
|
this.addMissingInternalContainersToProfile(pmcData);
|
||||||
|
|
||||||
|
// Change item IDs to be unique
|
||||||
|
pmcData.Inventory.items = this.itemHelper.replaceIDs(
|
||||||
|
pmcData.Inventory.items,
|
||||||
|
pmcData,
|
||||||
|
undefined,
|
||||||
|
pmcData.Inventory.fastPanel,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create profile
|
||||||
|
const profileDetails: ISptProfile = {
|
||||||
|
info: account,
|
||||||
|
characters: { pmc: pmcData, scav: {} as IPmcData },
|
||||||
|
suits: profileTemplateClone.suits,
|
||||||
|
userbuilds: profileTemplateClone.userbuilds,
|
||||||
|
dialogues: profileTemplateClone.dialogues,
|
||||||
|
spt: this.profileHelper.getDefaultSptDataObject(),
|
||||||
|
vitality: {} as IVitality,
|
||||||
|
inraid: {} as IInraid,
|
||||||
|
insurance: [],
|
||||||
|
traderPurchases: {},
|
||||||
|
achievements: {},
|
||||||
|
friends: [],
|
||||||
|
customisationUnlocks: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.addCustomisationUnlocksToProfile(profileDetails);
|
||||||
|
|
||||||
|
this.profileFixerService.checkForAndFixPmcProfileIssues(profileDetails.characters.pmc);
|
||||||
|
|
||||||
|
this.saveServer.addProfile(profileDetails);
|
||||||
|
|
||||||
|
if (profileTemplateClone.trader.setQuestsAvailableForStart) {
|
||||||
|
this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [QuestStatus.AvailableForStart]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Profile is flagged as wanting quests set to ready to hand in and collect rewards
|
||||||
|
if (profileTemplateClone.trader.setQuestsAvailableForFinish) {
|
||||||
|
this.questHelper.addAllQuestsToProfile(profileDetails.characters.pmc, [
|
||||||
|
QuestStatus.AvailableForStart,
|
||||||
|
QuestStatus.Started,
|
||||||
|
QuestStatus.AvailableForFinish,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Make unused response so applyQuestReward works
|
||||||
|
const response = this.eventOutputHolder.getOutput(sessionID);
|
||||||
|
|
||||||
|
// Add rewards for starting quests to profile
|
||||||
|
this.givePlayerStartingQuestRewards(profileDetails, sessionID, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.resetAllTradersInProfile(sessionID);
|
||||||
|
|
||||||
|
this.saveServer.getProfile(sessionID).characters.scav = this.playerScavGenerator.generate(sessionID);
|
||||||
|
|
||||||
|
// Store minimal profile and reload it
|
||||||
|
this.saveServer.saveProfile(sessionID);
|
||||||
|
this.saveServer.loadProfile(sessionID);
|
||||||
|
|
||||||
|
// Completed account creation
|
||||||
|
this.saveServer.getProfile(sessionID).info.wipe = false;
|
||||||
|
this.saveServer.saveProfile(sessionID);
|
||||||
|
|
||||||
|
return pmcData._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a profile
|
||||||
|
* @param sessionID Id of profile to delete
|
||||||
|
*/
|
||||||
|
protected deleteProfileBySessionId(sessionID: string): void {
|
||||||
|
if (sessionID in this.saveServer.getProfiles()) {
|
||||||
|
this.saveServer.deleteProfileById(sessionID);
|
||||||
|
} else {
|
||||||
|
this.logger.warning(
|
||||||
|
this.localisationService.getText("profile-unable_to_find_profile_by_id_cannot_delete", sessionID),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* make profiles pmcData.Inventory.equipment unique
|
||||||
|
* @param pmcData Profile to update
|
||||||
|
*/
|
||||||
|
protected updateInventoryEquipmentId(pmcData: IPmcData): void {
|
||||||
|
const oldEquipmentId = pmcData.Inventory.equipment;
|
||||||
|
pmcData.Inventory.equipment = this.hashUtil.generate();
|
||||||
|
|
||||||
|
for (const item of pmcData.Inventory.items) {
|
||||||
|
if (item.parentId === oldEquipmentId) {
|
||||||
|
item.parentId = pmcData.Inventory.equipment;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item._id === oldEquipmentId) {
|
||||||
|
item._id = pmcData.Inventory.equipment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each trader reset their state to what a level 1 player would see
|
||||||
|
* @param sessionId Session id of profile to reset
|
||||||
|
*/
|
||||||
|
protected resetAllTradersInProfile(sessionId: string): void {
|
||||||
|
for (const traderId in this.databaseService.getTraders()) {
|
||||||
|
this.traderHelper.resetTrader(sessionId, traderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a profile has the necessary internal containers e.g. questRaidItems / sortingTable
|
||||||
|
* DOES NOT check that stash exists
|
||||||
|
* @param pmcData Profile to check
|
||||||
|
*/
|
||||||
|
protected addMissingInternalContainersToProfile(pmcData: IPmcData): void {
|
||||||
|
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.hideoutCustomizationStashId)) {
|
||||||
|
pmcData.Inventory.items.push({
|
||||||
|
_id: pmcData.Inventory.hideoutCustomizationStashId,
|
||||||
|
_tpl: ItemTpl.HIDEOUTAREACONTAINER_CUSTOMIZATION,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.sortingTable)) {
|
||||||
|
pmcData.Inventory.items.push({
|
||||||
|
_id: pmcData.Inventory.sortingTable,
|
||||||
|
_tpl: ItemTpl.SORTINGTABLE_SORTING_TABLE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.questStashItems)) {
|
||||||
|
pmcData.Inventory.items.push({
|
||||||
|
_id: pmcData.Inventory.questStashItems,
|
||||||
|
_tpl: ItemTpl.STASH_QUESTOFFLINE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pmcData.Inventory.items.find((item) => item._id === pmcData.Inventory.questRaidItems)) {
|
||||||
|
pmcData.Inventory.items.push({
|
||||||
|
_id: pmcData.Inventory.questRaidItems,
|
||||||
|
_tpl: ItemTpl.STASH_QUESTRAID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addCustomisationUnlocksToProfile(fullProfile: ISptProfile) {
|
||||||
|
// Some game versions have additional dogtag variants, add them
|
||||||
|
switch (this.getGameEdition(fullProfile)) {
|
||||||
|
case GameEditions.EDGE_OF_DARKNESS:
|
||||||
|
// Gets EoD tags
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "6746fd09bafff85008048838",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "67471938bafff850080488b7",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case GameEditions.UNHEARD:
|
||||||
|
// Gets EoD+Unheard tags
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "6746fd09bafff85008048838",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "67471938bafff850080488b7",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "67471928d17d6431550563b5",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "6747193f170146228c0d2226",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pretigeLevel = fullProfile?.characters?.pmc?.Info?.PrestigeLevel;
|
||||||
|
if (pretigeLevel) {
|
||||||
|
if (pretigeLevel >= 1) {
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "674dbf593bee1152d407f005",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pretigeLevel >= 2) {
|
||||||
|
fullProfile.customisationUnlocks.push({
|
||||||
|
id: "675dcfea7ae1a8792107ca99",
|
||||||
|
source: CustomisationSource.DEFAULT,
|
||||||
|
type: CustomisationType.DOG_TAG,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getGameEdition(profile: ISptProfile): string {
|
||||||
|
const edition = profile.characters?.pmc?.Info?.GameVersion;
|
||||||
|
if (!edition) {
|
||||||
|
// Edge case - profile not created yet, fall back to what launcher has set
|
||||||
|
const launcherEdition = profile.info.edition;
|
||||||
|
switch (launcherEdition.toLowerCase()) {
|
||||||
|
case "edge of darkness":
|
||||||
|
return GameEditions.EDGE_OF_DARKNESS;
|
||||||
|
case "unheard":
|
||||||
|
return GameEditions.UNHEARD;
|
||||||
|
default:
|
||||||
|
return GameEditions.STANDARD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return edition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all quests in player profile, inspect rewards for the quests current state (accepted/completed)
|
||||||
|
* and send rewards to them in mail
|
||||||
|
* @param profileDetails Player profile
|
||||||
|
* @param sessionID Session id
|
||||||
|
* @param response Event router response
|
||||||
|
*/
|
||||||
|
protected givePlayerStartingQuestRewards(
|
||||||
|
profileDetails: ISptProfile,
|
||||||
|
sessionID: string,
|
||||||
|
response: IItemEventRouterResponse,
|
||||||
|
): void {
|
||||||
|
for (const quest of profileDetails.characters.pmc.Quests) {
|
||||||
|
const questFromDb = this.questHelper.getQuestFromDb(quest.qid, profileDetails.characters.pmc);
|
||||||
|
|
||||||
|
// Get messageId of text to send to player as text message in game
|
||||||
|
// Copy of code from QuestController.acceptQuest()
|
||||||
|
const messageId = this.questHelper.getMessageIdForQuestStart(
|
||||||
|
questFromDb.startedMessageText,
|
||||||
|
questFromDb.description,
|
||||||
|
);
|
||||||
|
const itemRewards = this.questRewardHelper.applyQuestReward(
|
||||||
|
profileDetails.characters.pmc,
|
||||||
|
quest.qid,
|
||||||
|
QuestStatus.Started,
|
||||||
|
sessionID,
|
||||||
|
response,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.mailSendService.sendLocalisedNpcMessageToPlayer(
|
||||||
|
sessionID,
|
||||||
|
this.traderHelper.getTraderById(questFromDb.traderId),
|
||||||
|
MessageType.QUEST_START,
|
||||||
|
messageId,
|
||||||
|
itemRewards,
|
||||||
|
this.timeUtil.getHoursAsSeconds(100),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ import { TraderHelper } from "@spt/helpers/TraderHelper";
|
|||||||
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
import { ILocationBase } from "@spt/models/eft/common/ILocationBase";
|
||||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||||
import { Common, IQuestStatus, ITraderInfo } from "@spt/models/eft/common/tables/IBotBase";
|
import { Common, IQuestStatus, ITraderInfo } from "@spt/models/eft/common/tables/IBotBase";
|
||||||
|
import { CustomisationSource } from "@spt/models/eft/common/tables/ICustomisationStorage";
|
||||||
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
||||||
import {
|
import {
|
||||||
IEndLocalRaidRequestData,
|
IEndLocalRaidRequestData,
|
||||||
@ -762,7 +763,7 @@ export class LocationLifecycleService {
|
|||||||
|
|
||||||
// Insert customisations into profile
|
// Insert customisations into profile
|
||||||
for (const reward of customisationRewards) {
|
for (const reward of customisationRewards) {
|
||||||
this.profileHelper.addHideoutCustomisationUnlock(fullProfile, reward, "achievement");
|
this.profileHelper.addHideoutCustomisationUnlock(fullProfile, reward, CustomisationSource.ACHIEVEMENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
/**
|
|
||||||
* Hydrate customisationStorage.json with data scraped together from other sources
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* - Run this script using npm: `npm run gen:customisationstorage`
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
import { dirname, join, resolve } from "node:path";
|
|
||||||
import { OnLoad } from "@spt/di/OnLoad";
|
|
||||||
import { IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
|
||||||
import type { ILogger } from "@spt/models/spt/utils/ILogger";
|
|
||||||
import { DatabaseServer } from "@spt/servers/DatabaseServer";
|
|
||||||
import { FileSystem } from "@spt/utils/FileSystem";
|
|
||||||
import { inject, injectAll, injectable } from "tsyringe";
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class HideoutCustomisationGen {
|
|
||||||
private questCustomisationReward: Record<string, IQuestReward[]> = {};
|
|
||||||
private achievementCustomisationReward: Record<string, IQuestReward[]> = {};
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@inject("DatabaseServer") protected databaseServer: DatabaseServer,
|
|
||||||
@inject("PrimaryLogger") protected logger: ILogger,
|
|
||||||
@inject("FileSystem") protected fileSystem: FileSystem,
|
|
||||||
@injectAll("OnLoad") protected onLoadComponents: OnLoad[],
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async run(): Promise<void> {
|
|
||||||
// Load all of the onload components, this gives us access to most of SPTs injections
|
|
||||||
for (const onLoad of this.onLoadComponents) {
|
|
||||||
await onLoad.onLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build up our dataset
|
|
||||||
this.buildQuestCustomisationList();
|
|
||||||
this.buildAchievementRewardCustomisationList();
|
|
||||||
this.updateCustomisationStorage();
|
|
||||||
|
|
||||||
// Dump the new data to disk
|
|
||||||
const currentDir = dirname(__filename);
|
|
||||||
const projectDir = resolve(currentDir, "..", "..", "..");
|
|
||||||
const templatesDir = join(projectDir, "assets", "database", "templates");
|
|
||||||
const customisationStorageOutPath = join(templatesDir, "customisationStorage.json");
|
|
||||||
await this.fileSystem.write(
|
|
||||||
customisationStorageOutPath,
|
|
||||||
JSON.stringify(this.databaseServer.getTables().templates?.customisationStorage, null, 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateCustomisationStorage(): void {
|
|
||||||
const customisationStoageDb = this.databaseServer.getTables().templates?.customisationStorage;
|
|
||||||
if (!customisationStoageDb) {
|
|
||||||
// no customisation storage in templates, nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const globalCustomisationDb of this.databaseServer.getTables().hideout?.customisation.globals) {
|
|
||||||
// Look for customisations that have a quest unlock condition
|
|
||||||
const questOrAchievementRequirement = globalCustomisationDb.conditions.find((condition) =>
|
|
||||||
["Quest", "Block"].includes(condition.conditionType),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!questOrAchievementRequirement) {
|
|
||||||
// Customisation doesnt have a requirement, skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customisationStoageDb.some((custStorageItem) => custStorageItem.id === globalCustomisationDb.id)) {
|
|
||||||
// Exists already in output destination file, skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const matchingQuest = this.questCustomisationReward[questOrAchievementRequirement.target as string];
|
|
||||||
const matchingAchievement =
|
|
||||||
this.achievementCustomisationReward[questOrAchievementRequirement.target as string];
|
|
||||||
|
|
||||||
let source = null;
|
|
||||||
if (matchingQuest) {
|
|
||||||
source = "unlockedInGame";
|
|
||||||
} else if (matchingAchievement) {
|
|
||||||
source = "achievement";
|
|
||||||
}
|
|
||||||
if (!source) {
|
|
||||||
this.logger.error(
|
|
||||||
`Found customisation to add but unable to establish source. Id: ${globalCustomisationDb.id} type: ${globalCustomisationDb.type}`,
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.success(
|
|
||||||
`Adding Id: ${globalCustomisationDb.id} Source: ${source} type: ${globalCustomisationDb.type}`,
|
|
||||||
);
|
|
||||||
customisationStoageDb.push({
|
|
||||||
id: globalCustomisationDb.id,
|
|
||||||
source: source,
|
|
||||||
type: globalCustomisationDb.type,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a dictionary of all quests with a `CustomizationDirect` reward
|
|
||||||
private buildQuestCustomisationList(): void {
|
|
||||||
for (const quest of Object.values(this.databaseServer.getTables().templates.quests)) {
|
|
||||||
const allRewards: IQuestReward[] = [
|
|
||||||
...quest.rewards.Fail,
|
|
||||||
...quest.rewards.Success,
|
|
||||||
...quest.rewards.Started,
|
|
||||||
];
|
|
||||||
const customisationDirectRewards = allRewards.filter((reward) => reward.type === "CustomizationDirect");
|
|
||||||
for (const directReward of customisationDirectRewards) {
|
|
||||||
if (!this.questCustomisationReward[quest._id]) {
|
|
||||||
this.questCustomisationReward[quest._id] = [];
|
|
||||||
}
|
|
||||||
this.questCustomisationReward[quest._id].push(directReward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a dictionary of all achievements with a `CustomizationDirect` reward
|
|
||||||
private buildAchievementRewardCustomisationList(): void {
|
|
||||||
for (const achievement of Object.values(this.databaseServer.getTables().templates?.achievements)) {
|
|
||||||
const allRewards: IQuestReward[] = Object.values(achievement.rewards);
|
|
||||||
const customisationDirectRewards = allRewards.filter((reward) => reward.type === "CustomizationDirect");
|
|
||||||
for (const directReward of customisationDirectRewards) {
|
|
||||||
if (!this.achievementCustomisationReward[achievement.id]) {
|
|
||||||
this.achievementCustomisationReward[achievement.id] = [];
|
|
||||||
}
|
|
||||||
this.achievementCustomisationReward[achievement.id].push(directReward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user