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" ;
2025-01-07 12:47:32 +00:00
import { ICustomisationStorage } from "@spt/models/eft/common/tables/ICustomisationStorage" ;
2024-11-24 15:47:03 +00:00
import { IItem } from "@spt/models/eft/common/tables/IItem" ;
2025-01-07 12:47:32 +00:00
import { IQuestReward } from "@spt/models/eft/common/tables/IQuest" ;
2024-12-13 21:25:32 -08:00
import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse" ;
2025-01-04 19:00:36 +00:00
import { ISpt , ISptProfile } from "@spt/models/eft/profile/ISptProfile" ;
2024-05-21 17:59:04 +00:00
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" ;
2025-01-06 18:09:33 +01:00
import type { ILogger } from "@spt/models/spt/utils/ILogger" ;
2024-05-21 17:59:04 +00:00
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" ;
2025-01-06 18:09:33 +01:00
import type { ICloner } from "@spt/utils/cloners/ICloner" ;
2024-07-23 11:12:53 -04:00
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
}
2025-01-04 19:00:36 +00:00
public getDefaultSptDataObject ( ) : ISpt {
return {
version : this.watermark.getVersionTag ( true ) ,
freeRepeatableRefreshUsedCount : { } ,
migrations : { } ,
cultistRewards : new Map ( ) ,
mods : [ ] ,
receivedGifts : [ ] ,
} ;
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-12-13 21:25:32 -08:00
/ * *
* Get full representation of a players profile JSON by the account ID , or undefined if not found
* @param accountId Account ID to find
* @returns
* /
public getFullProfileByAccountId ( accountID : string ) : ISptProfile | undefined {
const aid = Number . parseInt ( accountID ) ;
return Object . values ( this . saveServer . getProfiles ( ) ) . find ( ( profile ) = > profile ? . info ? . aid === aid ) ;
}
/ * *
* Retrieve a ChatRoomMember formatted profile for the given session ID
* @param sessionID The session ID to return the profile for
* @returns
* /
public getChatRoomMemberFromSessionId ( sessionID : string ) : ISearchFriendResponse | undefined {
const pmcProfile = this . getFullProfile ( sessionID ) ? . characters ? . pmc ;
if ( ! pmcProfile ) {
return undefined ;
}
return this . getChatRoomMemberFromPmcProfile ( pmcProfile ) ;
}
/ * *
* Retrieve a ChatRoomMember formatted profile for the given PMC profile data
* @param pmcProfile The PMC profile data to format into a ChatRoomMember structure
* @returns
* /
public getChatRoomMemberFromPmcProfile ( pmcProfile : IPmcData ) : ISearchFriendResponse {
return {
_id : pmcProfile._id ,
aid : pmcProfile.aid ,
Info : {
Nickname : pmcProfile.Info.Nickname ,
Side : pmcProfile.Info.Side ,
Level : pmcProfile.Info.Level ,
MemberCategory : pmcProfile.Info.MemberCategory ,
SelectedMemberCategory : pmcProfile.Info.SelectedMemberCategory ,
} ,
} ;
}
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
/ * *
2025-01-07 13:06:28 +00:00
* Add an achievement to player profile + check for and add any hideout customisation unlocks to profile
* @param fullProfile Profile to add achievement to
2024-06-04 15:36:01 +01:00
* @param achievementId Id of achievement to add
* /
2025-01-07 13:06:28 +00:00
public addAchievementToProfile ( fullProfile : ISptProfile , achievementId : string ) : void {
// Add achievement id to profile with timestamp it was unlocked
fullProfile . characters . pmc . Achievements [ achievementId ] = this . timeUtil . getTimestamp ( ) ;
// Check for any customisation unlocks
const achievementDataDb = this . databaseService
. getTemplates ( )
. achievements . find ( ( achievement ) = > achievement . id === achievementId ) ;
if ( ! achievementDataDb ) {
return ;
}
// Get customisation reward object from achievement db
const customizationDirectReward = achievementDataDb . rewards . find (
( reward ) = > reward . type === "CustomizationDirect" ,
) ;
if ( ! customizationDirectReward ) {
return ;
}
const customisationDataDb = this . databaseService
. getHideout ( )
. customisation . globals . find ( ( customisation ) = > customisation . itemId === customizationDirectReward . target ) ;
if ( ! customisationDataDb ) {
this . logger . error (
` Unable to find customisation data for ${ customizationDirectReward . target } in profile ${ fullProfile . info . id } ` ,
) ;
return ;
}
// Reward found, add to profile
fullProfile . customisationUnlocks . push ( {
id : customizationDirectReward.target ,
source : "achievement" ,
type : customisationDataDb . type ,
} ) ;
2024-06-04 15:36:01 +01:00
}
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
2024-12-07 13:50:09 -05:00
* @param profile
2024-12-06 09:14:19 -08:00
* @returns An array of IItem objects representing the favorited data
* /
public getOtherProfileFavorites ( profile : IPmcData ) : IItem [ ] {
let fullFavorites = [ ] ;
2024-12-07 13:50:09 -05:00
for ( const itemId of profile . Inventory . favoriteItems ? ? [ ] ) {
2024-12-06 09:14:19 -08:00
// 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 ) ;
2024-12-07 13:50:09 -05:00
if ( itemAndChildren && itemAndChildren . length > 0 ) {
2024-12-06 09:14:19 -08:00
// 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 ;
}
2025-01-07 12:47:32 +00:00
/ * *
* Store a hideout customisation unlock inside a profile
* @param fullProfile Profile to add unlock to
* @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 ;
}
2025-01-07 12:51:16 +00:00
fullProfile . customisationUnlocks || = [ ] ;
if ( fullProfile . customisationUnlocks ? . some ( ( unlock ) = > unlock . id === hideoutCustomisationDb . id ) ) {
2025-01-07 12:47:32 +00:00
this . logger . warning (
` Profile: ${ fullProfile . info . id } already has hideout customisaiton reward: ${ reward . target } , skipping ` ,
) ;
return ;
}
const rewardToStore : ICustomisationStorage = {
id : hideoutCustomisationDb.id ,
source : source ,
type : hideoutCustomisationDb . type ,
} ;
2025-01-07 12:51:16 +00:00
fullProfile . customisationUnlocks . push ( rewardToStore ) ;
2025-01-07 12:47:32 +00:00
}
2023-11-15 20:35:05 -05:00
}