Skip to content

Commit

Permalink
Merge branch '3.10.2-dev' into CultistCircleImprovements.3.10.2-dev
Browse files Browse the repository at this point in the history
  • Loading branch information
chompDev authored Dec 6, 2024
2 parents cb99adb + d34eca3 commit 4a6a80f
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 60 deletions.
23 changes: 8 additions & 15 deletions project/src/controllers/InventoryController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -928,24 +928,17 @@ export class InventoryController {
}

public setFavoriteItem(pmcData: IPmcData, request: ISetFavoriteItems, sessionId: string): void {
if (!pmcData.Inventory.favoriteItems) {
pmcData.Inventory.favoriteItems = [];
}
// The client sends the full list of favorite items, so clear the current favorites
pmcData.Inventory.favoriteItems = [];

for (const itemId of request.items) {
// If id already exists in array, we're removing it
const indexOfItemAlreadyFavorited = pmcData.Inventory.favoriteItems.findIndex((x) => x._id === itemId);
if (indexOfItemAlreadyFavorited > -1) {
pmcData.Inventory.favoriteItems.splice(indexOfItemAlreadyFavorited, 1);
} else {
const item = pmcData.Inventory.items.find((i) => i._id === itemId);

if (item === undefined) {
continue;
}

pmcData.Inventory.favoriteItems.push(item);
// Leaving this in as validation that the item exists in the profile
const item = pmcData.Inventory.items.find((i) => i._id === itemId);
if (item === undefined) {
continue;
}

pmcData.Inventory.favoriteItems.push(itemId);
}
}

Expand Down
5 changes: 4 additions & 1 deletion project/src/controllers/ProfileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,9 @@ export class ProfileController {
return response;
}

/**
* Handle client/profile/view
*/
public getOtherProfile(sessionId: string, request: IGetOtherProfileRequest): IGetOtherProfileResponse {
const player = this.profileHelper.getFullProfile(sessionId);
const playerPmc = player.characters.pmc;
Expand Down Expand Up @@ -431,7 +434,7 @@ export class ProfileController {
Items: playerPmc.Inventory.items,
},
achievements: playerPmc.Achievements,
favoriteItems: playerPmc.Inventory.favoriteItems ?? [],
favoriteItems: this.profileHelper.getOtherProfileFavorites(playerPmc),
pmcStats: {
eft: {
totalInGameTime: playerPmc.Stats.Eft.TotalInGameTime,
Expand Down
25 changes: 25 additions & 0 deletions project/src/helpers/ProfileHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,4 +533,29 @@ export class ProfileHelper {
public getQuestItemsInProfile(profile: IPmcData): IItem[] {
return profile.Inventory.items.filter((item) => item.parentId === profile.Inventory.questRaidItems);
}

/**
* 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;
}
}
2 changes: 1 addition & 1 deletion project/src/models/eft/common/tables/IBotBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export interface IInventory {
/** Key is hideout area enum numeric as string e.g. "24", value is area _id */
hideoutAreaStashes: Record<string, string>;
fastPanel: Record<string, string>;
favoriteItems: IItem[];
favoriteItems: string[];
}

export interface IBaseJsonSkills {
Expand Down
2 changes: 1 addition & 1 deletion project/src/services/BotNameService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class BotNameService {
const isPlayerScav = botGenerationDetails.isPlayerScav;
const simulateScavName = isPlayerScav ? false : this.shouldSimulatePlayerScavName(botRole);
const showTypeInNickname = this.botConfig.showTypeInNickname && !isPlayerScav;
const roleShouldBeUnique = uniqueRoles.includes(botRole.toLowerCase());
const roleShouldBeUnique = uniqueRoles?.includes(botRole.toLowerCase());

let isUnique = true;
let attempts = 0;
Expand Down
18 changes: 18 additions & 0 deletions project/src/services/ProfileFixerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class ProfileFixerService {
this.removeDanglingTaskConditionCounters(pmcProfile);
this.removeOrphanedQuests(pmcProfile);
this.verifyQuestProductionUnlocks(pmcProfile);
this.fixFavorites(pmcProfile);

if (pmcProfile.Hideout) {
this.addHideoutEliteSlots(pmcProfile);
Expand Down Expand Up @@ -341,6 +342,23 @@ export class ProfileFixerService {
}
}

/**
* Initial release of SPT 3.10 used an incorrect favorites structure, reformat
* the structure to the correct MongoID array structure
* @param pmcProfile
*/
protected fixFavorites(pmcProfile: IPmcData): void {
const favoritesAsAny = pmcProfile.Inventory?.favoriteItems as any;
if (favoritesAsAny)
{
const correctedFavorites = favoritesAsAny.map((favorite) => {
return favorite._id ?? favorite;
});

pmcProfile.Inventory.favoriteItems = correctedFavorites ?? [];
}
}

/**
* If the profile has elite Hideout Managment skill, add the additional slots from globals
* NOTE: This seems redundant, but we will leave it here just incase.
Expand Down
83 changes: 42 additions & 41 deletions project/tests/generators/BotGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import "reflect-metadata";
import { BotGenerator } from "@spt/generators/BotGenerator";
import { IPmcData } from "@spt/models/eft/common/IPmcData";
import { BotNameService } from "@spt/services/BotNameService";
import { container } from "tsyringe";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

describe("BotGenerator", () => {
let botGenerator: any;
let botNameService: any;

beforeEach(() => {
botGenerator = container.resolve<BotGenerator>("BotGenerator");
botNameService = container.resolve<BotNameService>("BotNameService");
});

afterEach(() => {
Expand Down Expand Up @@ -49,48 +49,48 @@ describe("BotGenerator", () => {
// });
// });

describe("generateBotNickname", () => {
describe("generateUniqueBotNickname", () => {
it("should choose random firstname for non player scav assault bot", () => {
const botJsonTemplate = { firstName: ["one", "two"], lastName: [] };
const botGenerationDetails = { isPlayerScav: false, isPmc: true, allPmcsHaveSameNameAsPlayer: false };
const botGenerationDetails = { isPlayerScav: false, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toMatch(/(one|two)/);
});

it("should choose random lastname for non player scav assault bot", () => {
const botJsonTemplate = { firstName: [], lastName: [["one", "two"]] };
const botGenerationDetails = { isPlayerScav: false, isPmc: true, allPmcsHaveSameNameAsPlayer: false };
const botGenerationDetails = { isPlayerScav: false, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toMatch(/(one|two)/);
});

it("should choose random firstname and lastname for non player scav assault bot", () => {
const botJsonTemplate = { firstName: ["first-one", "first-two"], lastName: [["last-one", "last-two"]] };
const botGenerationDetails = { isPlayerScav: false, isPmc: true, allPmcsHaveSameNameAsPlayer: false };
const botGenerationDetails = { isPlayerScav: false, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toMatch(/first-(one|two) last-(one|two)/);
});
Expand All @@ -100,12 +100,12 @@ describe("BotGenerator", () => {
const botGenerationDetails = { isPlayerScav: true, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toMatch(/(one|two)/);
});
Expand All @@ -115,12 +115,12 @@ describe("BotGenerator", () => {
const botGenerationDetails = { isPlayerScav: true, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toMatch(/(one|two)/);
});
Expand All @@ -130,28 +130,28 @@ describe("BotGenerator", () => {
const botGenerationDetails = { isPlayerScav: true, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toMatch(/first-(one|two) last-(one|two)/);
});

it("should append bot type to end of name when showTypeInNickname option is enabled ", () => {
const botJsonTemplate = { firstName: ["firstname"], lastName: ["lastname"] };
const botGenerationDetails = { isPlayerScav: false, isPmc: true, allPmcsHaveSameNameAsPlayer: false };
const botGenerationDetails = { isPlayerScav: false, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botGenerator.botConfig.showTypeInNickname = true;
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 0;
botNameService.botConfig.showTypeInNickname = true;

const mockPlayerProfile = { Info: { Nickname: "Player Nickname", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(result).toBe("firstname lastname assault");
});
Expand All @@ -161,35 +161,36 @@ describe("BotGenerator", () => {
const botGenerationDetails = { isPlayerScav: false, isPmc: true, allPmcsHaveSameNameAsPlayer: true };
const botRole = "assault";

botGenerator.botConfig.showTypeInNickname = false;
botGenerator.pmcConfig.addPrefixToSameNamePMCAsPlayerChance = 100;
botNameService.botConfig.showTypeInNickname = false;
botNameService.pmcConfig.addPrefixToSameNamePMCAsPlayerChance = 100;

const mockPlayerProfile = { Info: { Nickname: "player", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.botHelper, "getPmcNicknameOfMaxLength").mockReturnValue("player");

const getRandomTextThatMatchesPartialKeySpy = vi
.spyOn((botGenerator as any).localisationService, "getRandomTextThatMatchesPartialKey")
.spyOn((botNameService as any).localisationService, "getRandomTextThatMatchesPartialKey")
.mockReturnValue("test");

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);

expect(getRandomTextThatMatchesPartialKeySpy).toHaveBeenCalled();
expect(result).toBe("test player");
});

it("should generate PMC name in brackets behind scav name when chanceAssaultScavHasPlayerScavName is enabled", () => {
const botJsonTemplate = { firstName: ["scav"], lastName: [] };
const botGenerationDetails = { isPlayerScav: false, isPmc: true, allPmcsHaveSameNameAsPlayer: false };
const botGenerationDetails = { isPlayerScav: false, isPmc: false, allPmcsHaveSameNameAsPlayer: false };
const botRole = "assault";

botGenerator.botConfig.chanceAssaultScavHasPlayerScavName = 100;
botGenerator.databaseService.getBots().types.usec.firstName = ["player"];
botGenerator.databaseService.getBots().types.bear.firstName = [];
botNameService.botConfig.chanceAssaultScavHasPlayerScavName = 100;
botNameService.databaseService.getBots().types.usec.firstName = ["player"];
botNameService.databaseService.getBots().types.bear.firstName = [];

const mockPlayerProfile = { Info: { Nickname: "Player", Level: 1 } };
vi.spyOn(botGenerator.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);
vi.spyOn(botNameService.profileHelper, "getPmcProfile").mockReturnValue(<IPmcData>mockPlayerProfile);

const result = botGenerator.generateBotNickname(botJsonTemplate, botGenerationDetails, botRole);
const result = botNameService.generateUniqueBotNickname(botJsonTemplate, botGenerationDetails, botRole);
expect(result).toBe("scav (player)");
});
});
Expand Down
2 changes: 1 addition & 1 deletion project/tests/helpers/BotDifficultyHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe("BotHelper", () => {
});
const warningLogSpy = vi.spyOn(botDifficultyHelper.logger, "warning");

const result = botDifficultyHelper.getBotDifficultySettings("INVALID_TYPE", "normal");
const result = botDifficultyHelper.getBotDifficultySettings("INVALID_TYPE", "normal", { types: {} });
expect(result).toBe("test");
expect(warningLogSpy).toHaveBeenCalledTimes(1);
});
Expand Down

0 comments on commit 4a6a80f

Please sign in to comment.