2024-05-21 17:59:04 +00:00
|
|
|
import { ItemHelper } from "@spt/helpers/ItemHelper";
|
|
|
|
import { IPmcData } from "@spt/models/eft/common/IPmcData";
|
2024-09-24 11:26:45 +01:00
|
|
|
import { BanType, Common, ICounterKeyValue, IStats } from "@spt/models/eft/common/tables/IBotBase";
|
2024-11-24 15:47:03 +00:00
|
|
|
import { IItem } from "@spt/models/eft/common/tables/IItem";
|
2024-05-21 17:59:04 +00:00
|
|
|
import { ISptProfile } from "@spt/models/eft/profile/ISptProfile";
|
|
|
|
import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData";
|
|
|
|
import { AccountTypes } from "@spt/models/enums/AccountTypes";
|
|
|
|
import { BonusType } from "@spt/models/enums/BonusType";
|
|
|
|
import { ConfigTypes } from "@spt/models/enums/ConfigTypes";
|
2024-06-16 21:22:28 +01:00
|
|
|
import { GameEditions } from "@spt/models/enums/GameEditions";
|
2024-05-21 17:59:04 +00:00
|
|
|
import { SkillTypes } from "@spt/models/enums/SkillTypes";
|
|
|
|
import { IInventoryConfig } from "@spt/models/spt/config/IInventoryConfig";
|
|
|
|
import { ILogger } from "@spt/models/spt/utils/ILogger";
|
|
|
|
import { ConfigServer } from "@spt/servers/ConfigServer";
|
|
|
|
import { SaveServer } from "@spt/servers/SaveServer";
|
2024-05-29 15:15:45 +01:00
|
|
|
import { DatabaseService } from "@spt/services/DatabaseService";
|
2024-05-21 17:59:04 +00:00
|
|
|
import { LocalisationService } from "@spt/services/LocalisationService";
|
|
|
|
import { HashUtil } from "@spt/utils/HashUtil";
|
|
|
|
import { TimeUtil } from "@spt/utils/TimeUtil";
|
|
|
|
import { Watermark } from "@spt/utils/Watermark";
|
2024-07-23 11:12:53 -04:00
|
|
|
import { ICloner } from "@spt/utils/cloners/ICloner";
|
|
|
|
import { inject, injectable } from "tsyringe";
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
@injectable()
|
2024-07-23 11:12:53 -04:00
|
|
|
export class ProfileHelper {
|
2024-03-09 16:14:34 +00:00
|
|
|
protected inventoryConfig: IInventoryConfig;
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
constructor(
|
2024-05-28 14:04:20 +00:00
|
|
|
@inject("PrimaryLogger") protected logger: ILogger,
|
2024-02-26 23:51:45 +00:00
|
|
|
@inject("HashUtil") protected hashUtil: HashUtil,
|
2023-03-03 15:23:46 +00:00
|
|
|
@inject("Watermark") protected watermark: Watermark,
|
|
|
|
@inject("TimeUtil") protected timeUtil: TimeUtil,
|
|
|
|
@inject("SaveServer") protected saveServer: SaveServer,
|
2024-05-29 15:15:45 +01:00
|
|
|
@inject("DatabaseService") protected databaseService: DatabaseService,
|
2023-03-03 15:23:46 +00:00
|
|
|
@inject("ItemHelper") protected itemHelper: ItemHelper,
|
2023-11-15 20:35:05 -05:00
|
|
|
@inject("LocalisationService") protected localisationService: LocalisationService,
|
2024-03-09 16:14:34 +00:00
|
|
|
@inject("ConfigServer") protected configServer: ConfigServer,
|
2024-05-28 14:04:20 +00:00
|
|
|
@inject("PrimaryCloner") protected cloner: ICloner,
|
2024-07-23 11:12:53 -04:00
|
|
|
) {
|
2024-03-09 16:14:34 +00:00
|
|
|
this.inventoryConfig = this.configServer.getConfig(ConfigTypes.INVENTORY);
|
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-03-03 17:53:28 +00:00
|
|
|
/**
|
2023-10-10 11:03:20 +00:00
|
|
|
* Remove/reset a completed quest condtion from players profile quest data
|
2023-03-03 17:53:28 +00:00
|
|
|
* @param sessionID Session id
|
2023-10-10 11:03:20 +00:00
|
|
|
* @param questConditionId Quest with condition to remove
|
2023-03-03 17:53:28 +00:00
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public removeQuestConditionFromProfile(pmcData: IPmcData, questConditionId: Record<string, string>): void {
|
|
|
|
for (const questId in questConditionId) {
|
2023-10-10 11:03:20 +00:00
|
|
|
const conditionId = questConditionId[questId];
|
2024-05-17 15:32:41 -04:00
|
|
|
const profileQuest = pmcData.Quests.find((x) => x.qid === questId);
|
2023-10-10 11:03:20 +00:00
|
|
|
|
2023-03-03 17:53:28 +00:00
|
|
|
// Find index of condition in array
|
2023-10-10 11:03:20 +00:00
|
|
|
const index = profileQuest.completedConditions.indexOf(conditionId);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (index > -1) {
|
2023-03-03 17:53:28 +00:00
|
|
|
// Remove condition
|
2023-10-10 11:03:20 +00:00
|
|
|
profileQuest.completedConditions.splice(index, 1);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
}
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all profiles from server
|
|
|
|
* @returns Dictionary of profiles
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getProfiles(): Record<string, ISptProfile> {
|
2023-03-03 15:23:46 +00:00
|
|
|
return this.saveServer.getProfiles();
|
|
|
|
}
|
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Get the pmc and scav profiles as an array by profile id
|
2024-06-12 10:47:01 +01:00
|
|
|
* @param sessionId
|
2024-03-09 16:35:46 +00:00
|
|
|
* @returns Array of IPmcData objects
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getCompleteProfile(sessionId: string): IPmcData[] {
|
2023-03-03 15:23:46 +00:00
|
|
|
const output: IPmcData[] = [];
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
if (this.isWiped(sessionId)) {
|
2023-03-03 15:23:46 +00:00
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2024-09-03 06:09:23 +00:00
|
|
|
const fullProfileClone = this.cloner.clone(this.getFullProfile(sessionId));
|
|
|
|
|
|
|
|
// Sanitize any data the client can not receive
|
|
|
|
this.sanitizeProfileForClient(fullProfileClone);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2024-06-12 10:47:01 +01:00
|
|
|
// PMC must be at array index 0, scav at 1
|
2024-09-03 06:09:23 +00:00
|
|
|
output.push(fullProfileClone.characters.pmc);
|
|
|
|
output.push(fullProfileClone.characters.scav);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2024-09-03 06:09:23 +00:00
|
|
|
/**
|
|
|
|
* Sanitize any information from the profile that the client does not expect to receive
|
|
|
|
* @param clonedProfile A clone of the full player profile
|
|
|
|
*/
|
|
|
|
protected sanitizeProfileForClient(clonedProfile: ISptProfile) {
|
|
|
|
// Remove `loyaltyLevel` from `TradersInfo`, as otherwise it causes the client to not
|
|
|
|
// properly calculate the player's `loyaltyLevel`
|
|
|
|
for (const traderInfo of Object.values(clonedProfile.characters.pmc.TradersInfo)) {
|
|
|
|
traderInfo.loyaltyLevel = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-24 12:47:19 +01:00
|
|
|
/**
|
|
|
|
* Check if a nickname is used by another profile loaded by the server
|
2024-03-09 16:35:46 +00:00
|
|
|
* @param nicknameRequest nickname request object
|
2023-04-24 12:47:19 +01:00
|
|
|
* @param sessionID Session id
|
2024-06-12 10:47:01 +01:00
|
|
|
* @returns True if already in use
|
2023-04-24 12:47:19 +01:00
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public isNicknameTaken(nicknameRequest: IValidateNicknameRequestData, sessionID: string): boolean {
|
2024-06-12 10:47:01 +01:00
|
|
|
const allProfiles = Object.values(this.saveServer.getProfiles());
|
|
|
|
|
|
|
|
// Find a profile that doesn't have same session id but has same name
|
2024-07-23 11:12:53 -04:00
|
|
|
return allProfiles.some(
|
|
|
|
(profile) =>
|
|
|
|
this.profileHasInfoProperty(profile) &&
|
|
|
|
!this.stringsMatch(profile.info.id, sessionID) && // SessionIds dont match
|
|
|
|
this.stringsMatch(
|
|
|
|
// Nicknames do
|
|
|
|
profile.characters.pmc.Info.LowerNickname.toLowerCase(),
|
|
|
|
nicknameRequest.nickname.toLowerCase(),
|
|
|
|
),
|
2024-06-12 10:47:01 +01:00
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
protected profileHasInfoProperty(profile: ISptProfile): boolean {
|
2024-05-07 23:57:08 -04:00
|
|
|
return !!profile?.characters?.pmc?.Info;
|
2023-04-24 12:47:19 +01:00
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
protected stringsMatch(stringA: string, stringB: string): boolean {
|
2024-03-09 16:35:46 +00:00
|
|
|
return stringA === stringB;
|
2023-04-24 12:47:19 +01:00
|
|
|
}
|
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
/**
|
|
|
|
* Add experience to a PMC inside the players profile
|
|
|
|
* @param sessionID Session id
|
|
|
|
* @param experienceToAdd Experience to add to PMC character
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public addExperienceToPmc(sessionID: string, experienceToAdd: number): void {
|
2023-03-03 15:23:46 +00:00
|
|
|
const pmcData = this.getPmcProfile(sessionID);
|
|
|
|
pmcData.Info.Experience += experienceToAdd;
|
|
|
|
}
|
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Iterate all profiles and find matching pmc profile by provided id
|
|
|
|
* @param pmcId Profile id to find
|
|
|
|
* @returns IPmcData
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getProfileByPmcId(pmcId: string): IPmcData | undefined {
|
|
|
|
return Object.values(this.saveServer.getProfiles()).find((profile) => profile.characters.pmc?._id === pmcId)
|
|
|
|
?.characters.pmc;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
2024-06-12 10:47:01 +01:00
|
|
|
* Get experience value for given level
|
|
|
|
* @param level Level to get xp for
|
2024-03-09 16:35:46 +00:00
|
|
|
* @returns Number of xp points for level
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getExperience(level: number): number {
|
2024-02-05 18:51:32 -05:00
|
|
|
let playerLevel = level;
|
2024-05-29 15:15:45 +01:00
|
|
|
const expTable = this.databaseService.getGlobals().config.exp.level.exp_table;
|
2023-03-03 15:23:46 +00:00
|
|
|
let exp = 0;
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
if (playerLevel >= expTable.length) {
|
2023-03-03 15:23:46 +00:00
|
|
|
// make sure to not go out of bounds
|
2024-02-05 18:51:32 -05:00
|
|
|
playerLevel = expTable.length - 1;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
for (let i = 0; i < playerLevel; i++) {
|
2023-03-03 15:23:46 +00:00
|
|
|
exp += expTable[i].exp;
|
|
|
|
}
|
|
|
|
|
|
|
|
return exp;
|
|
|
|
}
|
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Get the max level a player can be
|
|
|
|
* @returns Max level
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getMaxLevel(): number {
|
2024-05-29 15:15:45 +01:00
|
|
|
return this.databaseService.getGlobals().config.exp.level.exp_table.length - 1;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
public getDefaultSptDataObject(): any {
|
2024-06-12 10:47:01 +01:00
|
|
|
return { version: this.watermark.getVersionTag(true) };
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Get full representation of a players profile json
|
|
|
|
* @param sessionID Profile id to get
|
2024-05-21 17:59:04 +00:00
|
|
|
* @returns ISptProfile object
|
2024-03-09 16:35:46 +00:00
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getFullProfile(sessionID: string): ISptProfile | undefined {
|
|
|
|
return this.saveServer.profileExists(sessionID) ? this.saveServer.getProfile(sessionID) : undefined;
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Get a PMC profile by its session id
|
|
|
|
* @param sessionID Profile id to return
|
|
|
|
* @returns IPmcData object
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getPmcProfile(sessionID: string): IPmcData | undefined {
|
2023-03-03 15:23:46 +00:00
|
|
|
const fullProfile = this.getFullProfile(sessionID);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!fullProfile?.characters?.pmc) {
|
2023-03-03 15:23:46 +00:00
|
|
|
return undefined;
|
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2023-03-03 15:23:46 +00:00
|
|
|
return this.saveServer.getProfile(sessionID).characters.pmc;
|
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2024-05-27 20:06:07 +00:00
|
|
|
/**
|
2024-06-12 10:47:01 +01:00
|
|
|
* Is given user id a player
|
|
|
|
* @param userId Id to validate
|
|
|
|
* @returns True is a player
|
2024-05-27 20:06:07 +00:00
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public isPlayer(userId: string): boolean {
|
2024-05-27 20:06:07 +00:00
|
|
|
return this.saveServer.profileExists(userId);
|
|
|
|
}
|
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Get a full profiles scav-specific sub-profile
|
|
|
|
* @param sessionID Profiles id
|
|
|
|
* @returns IPmcData object
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getScavProfile(sessionID: string): IPmcData {
|
2023-03-03 15:23:46 +00:00
|
|
|
return this.saveServer.getProfile(sessionID).characters.scav;
|
|
|
|
}
|
|
|
|
|
2023-07-17 09:36:10 +01:00
|
|
|
/**
|
|
|
|
* Get baseline counter values for a fresh profile
|
2024-03-09 16:35:46 +00:00
|
|
|
* @returns Default profile Stats object
|
2023-07-17 09:36:10 +01:00
|
|
|
*/
|
2024-09-24 11:26:45 +01:00
|
|
|
public getDefaultCounters(): IStats {
|
2023-03-03 15:23:46 +00:00
|
|
|
return {
|
2023-10-10 11:03:20 +00:00
|
|
|
Eft: {
|
|
|
|
CarriedQuestItems: [],
|
2024-07-23 17:30:20 +01:00
|
|
|
DamageHistory: { LethalDamagePart: "Head", LethalDamage: undefined, BodyParts: <any>[] },
|
2023-10-28 12:40:21 +01:00
|
|
|
DroppedItems: [],
|
|
|
|
ExperienceBonusMult: 0,
|
|
|
|
FoundInRaidItems: [],
|
|
|
|
LastPlayerState: undefined,
|
|
|
|
LastSessionDate: 0,
|
2023-10-10 11:03:20 +00:00
|
|
|
OverallCounters: { Items: [] },
|
2023-10-28 12:40:21 +01:00
|
|
|
SessionCounters: { Items: [] },
|
|
|
|
SessionExperienceMult: 0,
|
|
|
|
SurvivorClass: "Unknown",
|
|
|
|
TotalInGameTime: 0,
|
|
|
|
TotalSessionExperience: 0,
|
2023-11-15 20:35:05 -05:00
|
|
|
Victims: [],
|
|
|
|
},
|
2023-03-03 15:23:46 +00:00
|
|
|
};
|
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* is this profile flagged for data removal
|
|
|
|
* @param sessionID Profile id
|
|
|
|
* @returns True if profile is to be wiped of data/progress
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
protected isWiped(sessionID: string): boolean {
|
2023-03-03 15:23:46 +00:00
|
|
|
return this.saveServer.getProfile(sessionID).info.wipe;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Iterate over player profile inventory items and find the secure container and remove it
|
|
|
|
* @param profile Profile to remove secure container from
|
|
|
|
* @returns profile without secure container
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public removeSecureContainer(profile: IPmcData): IPmcData {
|
2023-03-03 15:23:46 +00:00
|
|
|
const items = profile.Inventory.items;
|
2024-05-17 15:32:41 -04:00
|
|
|
const secureContainer = items.find((x) => x.slotId === "SecuredContainer");
|
2024-07-23 11:12:53 -04:00
|
|
|
if (secureContainer) {
|
2023-04-24 12:47:29 +01:00
|
|
|
// Find and remove container + children
|
2023-11-15 20:35:05 -05:00
|
|
|
const childItemsInSecureContainer = this.itemHelper.findAndReturnChildrenByItems(
|
|
|
|
items,
|
|
|
|
secureContainer._id,
|
|
|
|
);
|
2023-03-03 15:23:46 +00:00
|
|
|
|
2023-04-24 12:47:29 +01:00
|
|
|
// Remove child items + secure container
|
2024-05-17 15:32:41 -04:00
|
|
|
profile.Inventory.items = items.filter((x) => !childItemsInSecureContainer.includes(x._id));
|
2023-03-03 15:23:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return profile;
|
|
|
|
}
|
2023-07-21 17:08:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag a profile as having received a gift
|
2024-05-21 17:59:04 +00:00
|
|
|
* Store giftid in profile spt object
|
2023-07-21 17:08:32 +00:00
|
|
|
* @param playerId Player to add gift flag to
|
|
|
|
* @param giftId Gift player received
|
2024-06-08 17:54:05 +01:00
|
|
|
* @param maxCount Limit of how many of this gift a player can have
|
2023-07-21 17:08:32 +00:00
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public flagGiftReceivedInProfile(playerId: string, giftId: string, maxCount: number): void {
|
2023-07-21 17:08:32 +00:00
|
|
|
const profileToUpdate = this.getFullProfile(playerId);
|
2024-06-08 17:54:05 +01:00
|
|
|
|
|
|
|
// nullguard receivedGifts
|
|
|
|
profileToUpdate.spt.receivedGifts ||= [];
|
|
|
|
|
|
|
|
const giftData = profileToUpdate.spt.receivedGifts.find((gift) => gift.giftId === giftId);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (giftData) {
|
2024-06-08 17:54:05 +01:00
|
|
|
// Increment counter
|
|
|
|
giftData.current++;
|
2024-06-09 09:13:38 +01:00
|
|
|
|
|
|
|
return;
|
2024-06-08 17:54:05 +01:00
|
|
|
}
|
2024-06-09 09:13:38 +01:00
|
|
|
|
|
|
|
// Player has never received gift, make a new object
|
2024-07-23 11:12:53 -04:00
|
|
|
profileToUpdate.spt.receivedGifts.push({
|
|
|
|
giftId: giftId,
|
|
|
|
timestampLastAccepted: this.timeUtil.getTimestamp(),
|
|
|
|
current: 1,
|
|
|
|
});
|
2023-07-21 17:08:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if profile has recieved a gift by id
|
|
|
|
* @param playerId Player profile to check for gift
|
|
|
|
* @param giftId Gift to check for
|
2024-06-09 09:13:38 +01:00
|
|
|
* @param maxGiftCount Max times gift can be given to player
|
2023-07-21 17:08:32 +00:00
|
|
|
* @returns True if player has recieved gift previously
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public playerHasRecievedMaxNumberOfGift(playerId: string, giftId: string, maxGiftCount: number): boolean {
|
2023-07-21 17:08:32 +00:00
|
|
|
const profile = this.getFullProfile(playerId);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!profile) {
|
2023-07-25 10:35:12 +01:00
|
|
|
this.logger.debug(`Unable to gift ${giftId}, profile: ${playerId} does not exist`);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-25 23:18:28 +01:00
|
|
|
if (!profile.spt?.receivedGifts) {
|
2023-07-21 17:08:32 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-07-25 23:18:28 +01:00
|
|
|
const giftDataFromProfile = profile.spt?.receivedGifts?.find((gift) => gift.giftId === giftId);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!giftDataFromProfile) {
|
2024-06-08 17:54:05 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-09 09:13:38 +01:00
|
|
|
return giftDataFromProfile.current >= maxGiftCount;
|
2023-07-21 17:08:32 +00:00
|
|
|
}
|
2023-07-24 18:47:26 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Find Stat in profile counters and increment by one
|
|
|
|
* @param counters Counters to search for key
|
|
|
|
* @param keyToIncrement Key
|
|
|
|
*/
|
2024-09-24 11:26:45 +01:00
|
|
|
public incrementStatCounter(counters: ICounterKeyValue[], keyToIncrement: string): void {
|
2024-05-17 15:32:41 -04:00
|
|
|
const stat = counters.find((x) => x.Key.includes(keyToIncrement));
|
2024-07-23 11:12:53 -04:00
|
|
|
if (stat) {
|
2023-07-24 18:47:26 +01:00
|
|
|
stat.Value++;
|
|
|
|
}
|
|
|
|
}
|
2023-11-07 09:58:58 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if player has a skill at elite level
|
|
|
|
* @param skillType Skill to check
|
|
|
|
* @param pmcProfile Profile to find skill in
|
|
|
|
* @returns True if player has skill at elite level
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public hasEliteSkillLevel(skillType: SkillTypes, pmcProfile: IPmcData): boolean {
|
2023-11-07 09:58:58 +00:00
|
|
|
const profileSkills = pmcProfile?.Skills?.Common;
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!profileSkills) {
|
2023-11-07 09:58:58 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-05-17 15:32:41 -04:00
|
|
|
const profileSkill = profileSkills.find((x) => x.Id === skillType);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!profileSkill) {
|
2023-11-07 09:58:58 +00:00
|
|
|
this.logger.warning(`Unable to check for elite skill ${skillType}, not found in profile`);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return profileSkill.Progress >= 5100; // level 51
|
|
|
|
}
|
2023-11-07 10:40:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add points to a specific skill in player profile
|
|
|
|
* @param skill Skill to add points to
|
|
|
|
* @param pointsToAdd Points to add
|
|
|
|
* @param pmcProfile Player profile with skill
|
|
|
|
* @param useSkillProgressRateMultipler Skills are multiplied by a value in globals, default is off to maintain compatibility with legacy code
|
2023-11-15 20:35:05 -05:00
|
|
|
* @returns
|
2023-11-07 10:40:14 +00:00
|
|
|
*/
|
2023-11-15 20:35:05 -05:00
|
|
|
public addSkillPointsToPlayer(
|
|
|
|
pmcProfile: IPmcData,
|
|
|
|
skill: SkillTypes,
|
|
|
|
pointsToAdd: number,
|
|
|
|
useSkillProgressRateMultipler = false,
|
2024-07-23 11:12:53 -04:00
|
|
|
): void {
|
2024-02-05 18:51:32 -05:00
|
|
|
let pointsToAddToSkill = pointsToAdd;
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!pointsToAddToSkill || pointsToAddToSkill < 0) {
|
2023-12-11 11:44:26 +00:00
|
|
|
this.logger.warning(
|
2023-11-15 20:35:05 -05:00
|
|
|
this.localisationService.getText("player-attempt_to_increment_skill_with_negative_value", skill),
|
|
|
|
);
|
2023-11-07 10:40:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const profileSkills = pmcProfile?.Skills?.Common;
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!profileSkills) {
|
2024-02-05 18:51:32 -05:00
|
|
|
this.logger.warning(`Unable to add ${pointsToAddToSkill} points to ${skill}, profile has no skills`);
|
2023-11-07 10:40:14 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-17 15:32:41 -04:00
|
|
|
const profileSkill = profileSkills.find((profileSkill) => profileSkill.Id === skill);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!profileSkill) {
|
2023-11-07 10:40:14 +00:00
|
|
|
this.logger.error(this.localisationService.getText("quest-no_skill_found", skill));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
if (useSkillProgressRateMultipler) {
|
2024-05-29 15:15:45 +01:00
|
|
|
const skillProgressRate = this.databaseService.getGlobals().config.SkillsSettings.SkillProgressRate;
|
2024-02-05 18:51:32 -05:00
|
|
|
pointsToAddToSkill *= skillProgressRate;
|
2023-11-07 10:40:14 +00:00
|
|
|
}
|
|
|
|
|
2024-03-09 16:14:34 +00:00
|
|
|
// Apply custom multipler to skill amount gained, if exists
|
2024-07-23 11:12:53 -04:00
|
|
|
if (this.inventoryConfig.skillGainMultiplers[skill]) {
|
2024-03-09 16:14:34 +00:00
|
|
|
pointsToAddToSkill *= this.inventoryConfig.skillGainMultiplers[skill];
|
|
|
|
}
|
|
|
|
|
2024-02-05 18:51:32 -05:00
|
|
|
profileSkill.Progress += pointsToAddToSkill;
|
2023-12-11 11:43:30 +00:00
|
|
|
profileSkill.Progress = Math.min(profileSkill.Progress, 5100); // Prevent skill from ever going above level 51 (5100)
|
2023-11-07 10:40:14 +00:00
|
|
|
profileSkill.LastAccess = this.timeUtil.getTimestamp();
|
|
|
|
}
|
2023-11-07 15:17:38 +00:00
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Get a speciic common skill from supplied profile
|
|
|
|
* @param pmcData Player profile
|
2024-03-30 11:32:14 +00:00
|
|
|
* @param skill Skill to look up and return value from
|
2024-03-09 16:35:46 +00:00
|
|
|
* @returns Common skill object from desired profile
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public getSkillFromProfile(pmcData: IPmcData, skill: SkillTypes): Common {
|
2024-05-17 15:32:41 -04:00
|
|
|
const skillToReturn = pmcData.Skills.Common.find((commonSkill) => commonSkill.Id === skill);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!skillToReturn) {
|
2023-11-07 15:17:38 +00:00
|
|
|
this.logger.warning(`Profile ${pmcData.sessionId} does not have a skill named: ${skill}`);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return skillToReturn;
|
|
|
|
}
|
2024-02-03 23:40:20 +00:00
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Is the provided session id for a developer account
|
|
|
|
* @param sessionID Profile id ot check
|
|
|
|
* @returns True if account is developer
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public isDeveloperAccount(sessionID: string): boolean {
|
2024-02-03 23:40:20 +00:00
|
|
|
return this.getFullProfile(sessionID).info.edition.toLowerCase().startsWith(AccountTypes.SPT_DEVELOPER);
|
|
|
|
}
|
2024-02-26 23:51:45 +00:00
|
|
|
|
2024-03-09 16:35:46 +00:00
|
|
|
/**
|
|
|
|
* Add stash row bonus to profile or increments rows given count if it already exists
|
|
|
|
* @param sessionId Profile id to give rows to
|
|
|
|
* @param rowsToAdd How many rows to give profile
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public addStashRowsBonusToProfile(sessionId: string, rowsToAdd: number): void {
|
2024-02-26 23:51:45 +00:00
|
|
|
const profile = this.getPmcProfile(sessionId);
|
2024-05-17 15:32:41 -04:00
|
|
|
const existingBonus = profile.Bonuses.find((bonus) => bonus.type === BonusType.STASH_ROWS);
|
2024-07-23 11:12:53 -04:00
|
|
|
if (!existingBonus) {
|
2024-02-26 23:51:45 +00:00
|
|
|
profile.Bonuses.push({
|
|
|
|
id: this.hashUtil.generate(),
|
|
|
|
value: rowsToAdd,
|
|
|
|
type: BonusType.STASH_ROWS,
|
|
|
|
passive: true,
|
|
|
|
visible: true,
|
|
|
|
production: false,
|
|
|
|
});
|
2024-07-23 11:12:53 -04:00
|
|
|
} else {
|
2024-02-26 23:51:45 +00:00
|
|
|
existingBonus.value += rowsToAdd;
|
|
|
|
}
|
|
|
|
}
|
2024-06-03 22:35:09 +01:00
|
|
|
|
2024-08-08 18:30:07 +01:00
|
|
|
/**
|
|
|
|
* Iterate over all bonuses and sum up all bonuses of desired type in provided profile
|
|
|
|
* @param pmcProfile Player profile
|
|
|
|
* @param desiredBonus Bonus to sum up
|
|
|
|
* @returns Summed bonus value or 0 if no bonus found
|
|
|
|
*/
|
|
|
|
public getBonusValueFromProfile(pmcProfile: IPmcData, desiredBonus: BonusType): number {
|
|
|
|
const bonuses = pmcProfile.Bonuses.filter((bonus) => bonus.type === desiredBonus);
|
2024-08-09 22:05:07 +01:00
|
|
|
if (bonuses.length === 0) {
|
2024-08-08 22:36:32 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2024-08-08 18:30:07 +01:00
|
|
|
|
|
|
|
// Sum all bonuses found above
|
2024-08-08 22:36:32 +01:00
|
|
|
return bonuses.reduce((sum, curr) => sum + (curr.value ?? 0), 0);
|
2024-08-08 18:30:07 +01:00
|
|
|
}
|
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
public playerIsFleaBanned(pmcProfile: IPmcData): boolean {
|
2024-06-03 22:35:09 +01:00
|
|
|
const currentTimestamp = this.timeUtil.getTimestamp();
|
2024-07-23 11:12:53 -04:00
|
|
|
return pmcProfile.Info.Bans.some((ban) => ban.banType === BanType.RAGFAIR && currentTimestamp < ban.dateTime);
|
2024-06-03 22:35:09 +01:00
|
|
|
}
|
2024-06-04 15:36:01 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an achievement to player profile
|
|
|
|
* @param pmcProfile Profile to add achievement to
|
|
|
|
* @param achievementId Id of achievement to add
|
|
|
|
*/
|
2024-07-23 11:12:53 -04:00
|
|
|
public addAchievementToProfile(pmcProfile: IPmcData, achievementId: string): void {
|
2024-06-04 15:36:01 +01:00
|
|
|
pmcProfile.Achievements[achievementId] = this.timeUtil.getTimestamp();
|
|
|
|
}
|
2024-06-16 10:58:35 +01:00
|
|
|
|
2024-07-23 11:12:53 -04:00
|
|
|
public hasAccessToRepeatableFreeRefreshSystem(pmcProfile: IPmcData): boolean {
|
|
|
|
return [GameEditions.EDGE_OF_DARKNESS, GameEditions.UNHEARD].includes(<any>pmcProfile.Info?.GameVersion);
|
2024-06-16 10:58:35 +01:00
|
|
|
}
|
2024-08-03 23:10:54 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Find a profiles "Pockets" item and replace its tpl with passed in value
|
|
|
|
* @param pmcProfile Player profile
|
|
|
|
* @param newPocketTpl New tpl to set profiles Pockets to
|
|
|
|
*/
|
|
|
|
public replaceProfilePocketTpl(pmcProfile: IPmcData, newPocketTpl: string): void {
|
2024-10-12 22:57:24 +01:00
|
|
|
// Find all pockets in profile, may be multiple as they could have equipment stand
|
|
|
|
// (1 pocket for each upgrade level of equipment stand)
|
|
|
|
const pockets = pmcProfile.Inventory.items.filter((item) => item.slotId === "Pockets");
|
|
|
|
if (pockets.length === 0) {
|
2024-08-03 23:10:54 +01:00
|
|
|
this.logger.error(
|
2024-10-12 22:57:24 +01:00
|
|
|
`Unable to replace profile: ${pmcProfile._id} pocket tpl with: ${newPocketTpl} as Pocket item could not be found.`,
|
2024-08-03 23:10:54 +01:00
|
|
|
);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-12 22:57:24 +01:00
|
|
|
for (const pocket of pockets) {
|
|
|
|
pocket._tpl = newPocketTpl;
|
|
|
|
}
|
2024-08-03 23:10:54 +01:00
|
|
|
}
|
2024-11-24 15:47:03 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return all quest items current in the supplied profile
|
|
|
|
* @param profile Profile to get quest items from
|
|
|
|
* @returns Array of item objects
|
|
|
|
*/
|
|
|
|
public getQuestItemsInProfile(profile: IPmcData): IItem[] {
|
|
|
|
return profile.Inventory.items.filter((item) => item.parentId === profile.Inventory.questRaidItems);
|
|
|
|
}
|
2024-12-06 09:14:19 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return a favorites array in the format expected by the getOtherProfile call
|
|
|
|
* @param profile
|
|
|
|
* @returns An array of IItem objects representing the favorited data
|
|
|
|
*/
|
|
|
|
public getOtherProfileFavorites(profile: IPmcData): IItem[] {
|
|
|
|
let fullFavorites = [];
|
|
|
|
|
|
|
|
for (const itemId of profile.Inventory.favoriteItems ?? [])
|
|
|
|
{
|
|
|
|
// When viewing another users profile, the client expects a full item with children, so get that
|
|
|
|
const itemAndChildren = this.itemHelper.findAndReturnChildrenAsItems(profile.Inventory.items, itemId);
|
|
|
|
if (itemAndChildren && itemAndChildren.length > 0)
|
|
|
|
{
|
|
|
|
// To get the client to actually see the items, we set the main item's parent to null, so it's treated as a root item
|
|
|
|
const clonedItems = this.cloner.clone(itemAndChildren);
|
|
|
|
clonedItems[0].parentId = null;
|
|
|
|
|
|
|
|
fullFavorites = fullFavorites.concat(clonedItems);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fullFavorites;
|
|
|
|
}
|
2023-11-15 20:35:05 -05:00
|
|
|
}
|