mirror of
https://github.com/sp-tarkov/server.git
synced 2025-02-13 09:50:43 -05:00
Merge branch '4.0.0-DEV' into json-validation-module
This commit is contained in:
commit
4b04b5d110
@ -22,7 +22,7 @@ export class PrestigeCallbacks {
|
||||
}
|
||||
|
||||
/** 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);
|
||||
|
||||
return this.httpResponse.nullResponse();
|
||||
|
@ -1,14 +1,23 @@
|
||||
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 { 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 { 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 { 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";
|
||||
@ -32,12 +41,14 @@ export class PrestigeController {
|
||||
@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,
|
||||
) {}
|
||||
@ -52,12 +63,125 @@ export class PrestigeController {
|
||||
/**
|
||||
* Handle /client/prestige/obtain
|
||||
*/
|
||||
public obtainPrestige(sessionID: string, request: IObtainPrestigeRequest): void {
|
||||
public obtainPrestige(sessionId: string, request: IObtainPrestigeRequest[]): void {
|
||||
// TODO
|
||||
// Reset profile back to default from template
|
||||
// Take items passed in from request and add to inventory
|
||||
// Set prestige level in profile
|
||||
// DONE Reset profile back to default from template
|
||||
// DONE Set prestige level in profile
|
||||
// DONE Copy skills
|
||||
// DONE Take items passed in from request and add to inventory
|
||||
// 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 { DialogueHelper } from "@spt/helpers/DialogueHelper";
|
||||
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 { 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 { IGetProfileStatusResponseData } from "@spt/models/eft/profile/GetProfileStatusResponseData";
|
||||
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 { ISearchFriendRequestData } from "@spt/models/eft/profile/ISearchFriendRequestData";
|
||||
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 { 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 { 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";
|
||||
|
||||
@ -41,22 +23,11 @@ import { inject, injectable } from "tsyringe";
|
||||
export class ProfileController {
|
||||
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("SeasonalEventService") protected seasonalEventService: SeasonalEventService,
|
||||
@inject("MailSendService") protected mailSendService: MailSendService,
|
||||
@inject("CreateProfileService") protected createProfileService: CreateProfileService,
|
||||
@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,
|
||||
) {}
|
||||
|
||||
@ -129,311 +100,7 @@ export class ProfileController {
|
||||
* @returns Profiles _id value
|
||||
*/
|
||||
public createProfile(info: IProfileCreateRequestData, sessionID: string): 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 = {};
|
||||
|
||||
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);
|
||||
}
|
||||
return this.createProfileService.createProfile(sessionID, info);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,6 +209,7 @@ import { BotLootCacheService } from "@spt/services/BotLootCacheService";
|
||||
import { BotNameService } from "@spt/services/BotNameService";
|
||||
import { BotWeaponModLimitService } from "@spt/services/BotWeaponModLimitService";
|
||||
import { CircleOfCultistService } from "@spt/services/CircleOfCultistService";
|
||||
import { CreateProfileService } from "@spt/services/CreateProfileService";
|
||||
import { CustomLocationWaveService } from "@spt/services/CustomLocationWaveService";
|
||||
import { DatabaseService } from "@spt/services/DatabaseService";
|
||||
import { FenceService } from "@spt/services/FenceService";
|
||||
@ -820,6 +821,9 @@ export class Container {
|
||||
depContainer.register<PostDbLoadService>("PostDbLoadService", PostDbLoadService, {
|
||||
lifecycle: Lifecycle.Singleton,
|
||||
});
|
||||
depContainer.register<CreateProfileService>("CreateProfileService", CreateProfileService, {
|
||||
lifecycle: Lifecycle.Singleton,
|
||||
});
|
||||
}
|
||||
|
||||
private static registerServers(depContainer: DependencyContainer): void {
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
||||
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
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 { IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
||||
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse";
|
||||
@ -582,8 +586,8 @@ export class ProfileHelper {
|
||||
// Reward found, add to profile
|
||||
fullProfile.customisationUnlocks.push({
|
||||
id: customizationDirectReward.target,
|
||||
source: "achievement",
|
||||
type: customisationDataDb.type,
|
||||
source: CustomisationSource.ACHIEVEMENT,
|
||||
type: customisationDataDb.type as CustomisationType,
|
||||
});
|
||||
}
|
||||
|
||||
@ -651,33 +655,75 @@ export class ProfileHelper {
|
||||
* @param reward reward given to player with customisation data
|
||||
* @param source Source of reward, e.g. "unlockedInGame" for quests and "achievement" for achievements
|
||||
*/
|
||||
public addHideoutCustomisationUnlock(fullProfile: ISptProfile, reward: IQuestReward, source: string): void {
|
||||
// Get matching db data for reward
|
||||
const hideoutCustomisationDb = this.databaseService
|
||||
.getHideout()
|
||||
.customisation.globals.find((customisation) => customisation.itemId === reward.target);
|
||||
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;
|
||||
}
|
||||
|
||||
public addHideoutCustomisationUnlock(
|
||||
fullProfile: ISptProfile,
|
||||
reward: IQuestReward,
|
||||
source: CustomisationSource,
|
||||
): void {
|
||||
fullProfile.customisationUnlocks ||= [];
|
||||
if (fullProfile.customisationUnlocks?.some((unlock) => unlock.id === hideoutCustomisationDb.id)) {
|
||||
if (fullProfile.customisationUnlocks?.some((unlock) => unlock.id === reward.target)) {
|
||||
this.logger.warning(
|
||||
`Profile: ${fullProfile.info.id} already has hideout customisaiton reward: ${reward.target}, skipping`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const customisationTemplateDb = this.databaseService.getTemplates().customization;
|
||||
const matchingCustomisation = customisationTemplateDb[reward.target];
|
||||
|
||||
if (matchingCustomisation) {
|
||||
const rewardToStore: ICustomisationStorage = {
|
||||
id: hideoutCustomisationDb.itemId,
|
||||
id: reward.target,
|
||||
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);
|
||||
}
|
||||
|
||||
// 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 { TraderHelper } from "@spt/helpers/TraderHelper";
|
||||
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 { IQuest, IQuestReward } from "@spt/models/eft/common/tables/IQuest";
|
||||
import { IHideoutProduction } from "@spt/models/eft/hideout/IHideoutProduction";
|
||||
@ -131,7 +132,11 @@ export class QuestRewardHelper {
|
||||
this.profileHelper.replaceProfilePocketTpl(pmcProfile, reward.target);
|
||||
break;
|
||||
case QuestRewardType.CUSTOMIZATION_DIRECT:
|
||||
this.profileHelper.addHideoutCustomisationUnlock(fullProfile, reward, "unlockedInGame");
|
||||
this.profileHelper.addHideoutCustomisationUnlock(
|
||||
fullProfile,
|
||||
reward,
|
||||
CustomisationSource.UNLOCKED_IN_GAME,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
this.logger.error(
|
||||
|
@ -1,5 +1,31 @@
|
||||
export interface ICustomisationStorage {
|
||||
id: string; // Customiastion.json/itemId
|
||||
source: string;
|
||||
type: string;
|
||||
source: CustomisationSource;
|
||||
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";
|
||||
|
||||
export interface IPrestige {
|
||||
elements: IPretigeElement;
|
||||
elements: IPretigeElement[];
|
||||
}
|
||||
|
||||
export interface IPretigeElement {
|
||||
|
@ -3,4 +3,5 @@ export interface IProfileCreateRequestData {
|
||||
nickname: string;
|
||||
headId: string;
|
||||
voiceId: string;
|
||||
sptForcePrestigeLevel?: number;
|
||||
}
|
||||
|
@ -12,4 +12,6 @@ export enum QuestRewardType {
|
||||
ACHIEVEMENT = "Achievement",
|
||||
POCKETS = "Pockets",
|
||||
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 { IPmcData } from "@spt/models/eft/common/IPmcData";
|
||||
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 {
|
||||
IEndLocalRaidRequestData,
|
||||
@ -762,7 +763,7 @@ export class LocationLifecycleService {
|
||||
|
||||
// Insert customisations into profile
|
||||
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