diff --git a/project/src/callbacks/DialogueCallbacks.ts b/project/src/callbacks/DialogueCallbacks.ts index 9e931cf1..eb165230 100644 --- a/project/src/callbacks/DialogueCallbacks.ts +++ b/project/src/callbacks/DialogueCallbacks.ts @@ -220,6 +220,7 @@ export class DialogueCallbacks implements OnUpdate { /** Handle client/friend/delete */ public deleteFriend(url: string, request: IDeleteFriendRequest, sessionID: string): INullResponseData { + this.httpResponse.getBody(this.dialogueController.deleteFriend(sessionID, request)); return this.httpResponse.nullResponse(); } diff --git a/project/src/controllers/DialogueController.ts b/project/src/controllers/DialogueController.ts index 582ee197..64236062 100644 --- a/project/src/controllers/DialogueController.ts +++ b/project/src/controllers/DialogueController.ts @@ -1,5 +1,8 @@ import { IDialogueChatBot } from "@spt/helpers/Dialogue/IDialogueChatBot"; import { DialogueHelper } from "@spt/helpers/DialogueHelper"; +import { NotificationSendHelper } from "@spt/helpers/NotificationSendHelper"; +import { ProfileHelper } from "@spt/helpers/ProfileHelper"; +import { IDeleteFriendRequest } from "@spt/models/eft/dialog/IDeleteFriendRequest"; import { IFriendRequestData } from "@spt/models/eft/dialog/IFriendRequestData"; import { IFriendRequestSendResponse } from "@spt/models/eft/dialog/IFriendRequestSendResponse"; import { IGetAllAttachmentsResponse } from "@spt/models/eft/dialog/IGetAllAttachmentsResponse"; @@ -8,8 +11,11 @@ import { IGetMailDialogViewRequestData } from "@spt/models/eft/dialog/IGetMailDi import { IGetMailDialogViewResponseData } from "@spt/models/eft/dialog/IGetMailDialogViewResponseData"; import { ISendMessageRequest } from "@spt/models/eft/dialog/ISendMessageRequest"; import { IDialogue, IDialogueInfo, IMessage, ISptProfile, IUserDialogInfo } from "@spt/models/eft/profile/ISptProfile"; +import { IWsFriendsListAccept } from "@spt/models/eft/ws/IWsFriendsListAccept"; +import { BackendErrorCodes } from "@spt/models/enums/BackendErrorCodes"; import { ConfigTypes } from "@spt/models/enums/ConfigTypes"; import { MessageType } from "@spt/models/enums/MessageType"; +import { NotificationEventType } from "@spt/models/enums/NotificationEventType"; import { ICoreConfig } from "@spt/models/spt/config/ICoreConfig"; import { ILogger } from "@spt/models/spt/utils/ILogger"; import { ConfigServer } from "@spt/servers/ConfigServer"; @@ -26,6 +32,8 @@ export class DialogueController { @inject("SaveServer") protected saveServer: SaveServer, @inject("TimeUtil") protected timeUtil: TimeUtil, @inject("DialogueHelper") protected dialogueHelper: DialogueHelper, + @inject("NotificationSendHelper") protected notificationSendHelper: NotificationSendHelper, + @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("MailSendService") protected mailSendService: MailSendService, @inject("LocalisationService") protected localisationService: LocalisationService, @inject("ConfigServer") protected configServer: ConfigServer, @@ -69,8 +77,19 @@ export class DialogueController { * @returns IGetFriendListDataResponse */ public getFriendList(sessionID: string): IGetFriendListDataResponse { - // Force a fake friend called SPT into friend list - return { Friends: this.dialogueChatBots.map((v) => v.getChatBot()), Ignore: [], InIgnoreList: [] }; + // Add all chatbots to the friends list + const friends = this.dialogueChatBots.map((v) => v.getChatBot()); + + // Add any friends the user has after the chatbots + const profile = this.profileHelper.getFullProfile(sessionID); + for (const friendId of profile?.friends) { + const friendProfile = this.profileHelper.getChatRoomMemberFromSessionId(friendId); + if (friendProfile) { + friends.push(friendProfile); + } + } + + return { Friends: friends, Ignore: [], InIgnoreList: [] }; } /** @@ -441,6 +460,46 @@ export class DialogueController { /** Handle client/friend/request/send */ public sendFriendRequest(sessionID: string, request: IFriendRequestData): IFriendRequestSendResponse { - return { status: 0, requestId: "12345", retryAfter: 600 }; + // To avoid needing to jump between profiles, auto-accept all friend requests + const friendProfile = this.profileHelper.getFullProfile(request.to); + if (!friendProfile?.characters?.pmc) { + return { + status: BackendErrorCodes.PLAYERPROFILENOTFOUND, + requestId: "", // Unused in an error state + retryAfter: 600, + }; + } + + // Only add the profile to the friends list if it doesn't already exist + const profile = this.saveServer.getProfile(sessionID); + if (!profile.friends.includes(request.to)) { + profile.friends.push(request.to); + } + + // We need to delay this so that the friend request gets properly added to the clientside list before we accept it + setTimeout(() => { + const notification: IWsFriendsListAccept = { + // TODO: eventId isn't necessary, but the interface requires it, so we can use a dummy value. Interface should be updated + eventId: "0", + type: NotificationEventType.FRIEND_LIST_REQUEST_ACCEPTED, + profile: this.profileHelper.getChatRoomMemberFromPmcProfile(friendProfile.characters.pmc), + }; + this.notificationSendHelper.sendMessage(sessionID, notification); + }, 1000); + + return { + status: BackendErrorCodes.NONE, + requestId: friendProfile.info.aid.toString(), + retryAfter: 600, + }; + } + + /** Handle client/friend/delete */ + public deleteFriend(sessionID: string, request: IDeleteFriendRequest): void { + const profile = this.saveServer.getProfile(sessionID); + const friendIndex = profile.friends.indexOf(request.friend_id); + if (friendIndex !== -1) { + profile.friends.splice(friendIndex, 1); + } } } diff --git a/project/src/controllers/GameController.ts b/project/src/controllers/GameController.ts index a66216f6..394d9c75 100644 --- a/project/src/controllers/GameController.ts +++ b/project/src/controllers/GameController.ts @@ -122,6 +122,11 @@ export class GameController { fullProfile.spt.cultistRewards = new Map(); } + // Make sure we have a friends list array + if (typeof fullProfile.friends === "undefined") { + fullProfile.friends = []; + } + //3.9 migrations if (fullProfile.spt.version.includes("3.9.") && !fullProfile.spt.migrations["39x"]) { // Check every item has a valid mongoid diff --git a/project/src/controllers/ProfileController.ts b/project/src/controllers/ProfileController.ts index 4fa9b033..fed218c2 100644 --- a/project/src/controllers/ProfileController.ts +++ b/project/src/controllers/ProfileController.ts @@ -19,7 +19,6 @@ import { ISearchFriendRequestData } from "@spt/models/eft/profile/ISearchFriendR 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 { ItemTpl } from "@spt/models/enums/ItemTpl"; import { MessageType } from "@spt/models/enums/MessageType"; import { QuestStatus } from "@spt/models/enums/QuestStatus"; import { ILogger } from "@spt/models/spt/utils/ILogger"; @@ -182,6 +181,7 @@ export class ProfileController { insurance: [], traderPurchases: {}, achievements: {}, + friends: [], }; this.profileFixerService.checkForAndFixPmcProfileIssues(profileDetails.characters.pmc); @@ -361,21 +361,23 @@ export class ProfileController { * Handle client/game/profile/search */ public getFriends(info: ISearchFriendRequestData, sessionID: string): ISearchFriendResponse[] { - const profile = this.saveServer.getProfile(sessionID); + // TODO: We should probably rename this method in the next client update + const result: ISearchFriendResponse[] = []; - // return some of the current player info for now - return [ - { - _id: profile.characters.pmc._id, - aid: profile.characters.pmc.aid, - Info: { - Nickname: info.nickname, - Side: "Bear", - Level: 1, - MemberCategory: profile.characters.pmc.Info.MemberCategory, - }, - }, - ]; + // Find any profiles with a nickname containing the entered name + const allProfiles = Object.values(this.saveServer.getProfiles()); + + for (const profile of allProfiles) { + const pmcProfile = profile.characters.pmc; + + if (!pmcProfile.Info.LowerNickname.includes(info.nickname.toLocaleLowerCase())) { + continue; + } + + result.push(this.profileHelper.getChatRoomMemberFromPmcProfile(pmcProfile)); + } + + return result; } /** @@ -405,11 +407,14 @@ export class ProfileController { * Handle client/profile/view */ public getOtherProfile(sessionId: string, request: IGetOtherProfileRequest): IGetOtherProfileResponse { - const player = this.profileHelper.getFullProfile(sessionId); - const playerPmc = player.characters.pmc; - const playerScav = player.characters.scav; + // Find the profile by the account ID, fall back to the current player if we can't find the account + let profile = this.profileHelper.getFullProfileByAccountId(request.accountId); + if (!profile?.characters?.pmc || !profile?.characters?.scav) { + profile = this.profileHelper.getFullProfile(sessionId); + } + const playerPmc = profile.characters.pmc; + const playerScav = profile.characters.scav; - // return player for now return { id: playerPmc._id, aid: playerPmc.aid, diff --git a/project/src/helpers/ProfileHelper.ts b/project/src/helpers/ProfileHelper.ts index e65d9f64..4618c19a 100644 --- a/project/src/helpers/ProfileHelper.ts +++ b/project/src/helpers/ProfileHelper.ts @@ -2,6 +2,7 @@ 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 { IItem } from "@spt/models/eft/common/tables/IItem"; +import { ISearchFriendResponse } from "@spt/models/eft/profile/ISearchFriendResponse"; import { ISptProfile } from "@spt/models/eft/profile/ISptProfile"; import { IValidateNicknameRequestData } from "@spt/models/eft/profile/IValidateNicknameRequestData"; import { AccountTypes } from "@spt/models/enums/AccountTypes"; @@ -196,6 +197,49 @@ export class ProfileHelper { return this.saveServer.profileExists(sessionID) ? this.saveServer.getProfile(sessionID) : undefined; } + /** + * 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, + }, + }; + } + /** * Get a PMC profile by its session id * @param sessionID Profile id to return diff --git a/project/src/models/eft/profile/ISearchFriendResponse.ts b/project/src/models/eft/profile/ISearchFriendResponse.ts index 393ecbe7..0325d671 100644 --- a/project/src/models/eft/profile/ISearchFriendResponse.ts +++ b/project/src/models/eft/profile/ISearchFriendResponse.ts @@ -9,4 +9,5 @@ export interface Info { Side: string; Level: number; MemberCategory: number; + SelectedMemberCategory: number; } diff --git a/project/src/models/eft/profile/ISptProfile.ts b/project/src/models/eft/profile/ISptProfile.ts index 97162460..367aa998 100644 --- a/project/src/models/eft/profile/ISptProfile.ts +++ b/project/src/models/eft/profile/ISptProfile.ts @@ -20,6 +20,8 @@ export interface ISptProfile { traderPurchases?: Record>; /** Achievements earned by player */ achievements: Record; + /** List of friend profile IDs */ + friends: string[]; } export class ITraderPurchaseData { diff --git a/project/src/models/eft/ws/IWsFriendsListAccept.ts b/project/src/models/eft/ws/IWsFriendsListAccept.ts new file mode 100644 index 00000000..047a51c3 --- /dev/null +++ b/project/src/models/eft/ws/IWsFriendsListAccept.ts @@ -0,0 +1,6 @@ +import { IWsNotificationEvent } from "@spt/models/eft/ws/IWsNotificationEvent"; +import { ISearchFriendResponse } from "../profile/ISearchFriendResponse"; + +export interface IWsFriendsListAccept extends IWsNotificationEvent { + profile: ISearchFriendResponse; +}