diff --git a/project/src/callbacks/InventoryCallbacks.ts b/project/src/callbacks/InventoryCallbacks.ts index 22dd7145..c047fc2d 100644 --- a/project/src/callbacks/InventoryCallbacks.ts +++ b/project/src/callbacks/InventoryCallbacks.ts @@ -19,6 +19,7 @@ import { IInventoryTagRequestData } from "@spt-aki/models/eft/inventory/IInvento import { IInventoryToggleRequestData } from "@spt-aki/models/eft/inventory/IInventoryToggleRequestData"; import { IInventoryTransferRequestData } from "@spt-aki/models/eft/inventory/IInventoryTransferRequestData"; import { IOpenRandomLootContainerRequestData } from "@spt-aki/models/eft/inventory/IOpenRandomLootContainerRequestData"; +import { IRedeemProfileRequestData } from "@spt-aki/models/eft/inventory/IRedeemProfileRequestData"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; @injectable() @@ -156,4 +157,12 @@ export class InventoryCallbacks { return this.inventoryController.openRandomLootContainer(pmcData, body, sessionID); } + + public redeemProfileReward(pmcData: IPmcData, + body: IRedeemProfileRequestData, + sessionId: string + ): IItemEventRouterResponse + { + return this.inventoryController.redeemProfileReward(pmcData, body, sessionId) + } } diff --git a/project/src/controllers/InventoryController.ts b/project/src/controllers/InventoryController.ts index ebbf3504..80c6728a 100644 --- a/project/src/controllers/InventoryController.ts +++ b/project/src/controllers/InventoryController.ts @@ -27,6 +27,7 @@ import { IInventoryTagRequestData } from "@spt-aki/models/eft/inventory/IInvento import { IInventoryToggleRequestData } from "@spt-aki/models/eft/inventory/IInventoryToggleRequestData"; import { IInventoryTransferRequestData } from "@spt-aki/models/eft/inventory/IInventoryTransferRequestData"; import { IOpenRandomLootContainerRequestData } from "@spt-aki/models/eft/inventory/IOpenRandomLootContainerRequestData"; +import { IRedeemProfileRequestData } from "@spt-aki/models/eft/inventory/IRedeemProfileRequestData"; import { IItemEventRouterResponse } from "@spt-aki/models/eft/itemEvent/IItemEventRouterResponse"; import { BackendErrorCodes } from "@spt-aki/models/enums/BackendErrorCodes"; import { SkillTypes } from "@spt-aki/models/enums/SkillTypes"; @@ -36,6 +37,7 @@ import { EventOutputHolder } from "@spt-aki/routers/EventOutputHolder"; import { DatabaseServer } from "@spt-aki/servers/DatabaseServer"; import { FenceService } from "@spt-aki/services/FenceService"; import { LocalisationService } from "@spt-aki/services/LocalisationService"; +import { PlayerService } from "@spt-aki/services/PlayerService"; import { RagfairOfferService } from "@spt-aki/services/RagfairOfferService"; import { HashUtil } from "@spt-aki/utils/HashUtil"; import { HttpResponseUtil } from "@spt-aki/utils/HttpResponseUtil"; @@ -60,6 +62,7 @@ export class InventoryController @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("PaymentHelper") protected paymentHelper: PaymentHelper, @inject("LocalisationService") protected localisationService: LocalisationService, + @inject("PlayerService") protected playerService: PlayerService, @inject("LootGenerator") protected lootGenerator: LootGenerator, @inject("EventOutputHolder") protected eventOutputHolder: EventOutputHolder, @inject("HttpResponseUtil") protected httpResponseUtil: HttpResponseUtil, @@ -649,19 +652,32 @@ export class InventoryController if (itemId) { - // item found - const item = this.databaseServer.getTables().templates.items[itemId]; - - pmcData.Info.Experience += item._props.ExamineExperience; - pmcData.Encyclopedia[itemId] = true; - - // TODO: update this with correct calculation using values from globals json - this.profileHelper.addSkillPointsToPlayer(pmcData, SkillTypes.INTELLECT, 0.5); + this.flagItemsAsInspectedAndRewardXp([itemId], pmcData); } return this.eventOutputHolder.getOutput(sessionID); } + protected flagItemsAsInspectedAndRewardXp(itemTpls: string[], pmcProfile: IPmcData): void + { + for (const itemTpl of itemTpls) + { + // item found + const item = this.databaseServer.getTables().templates.items[itemTpl]; + if (!item) + { + this.logger.warning(`Unable to find item with id ${itemTpl}, skipping inspection`) + return; + } + + pmcProfile.Info.Experience += item._props.ExamineExperience; + pmcProfile.Encyclopedia[itemTpl] = true; + } + + // TODO: update this with correct calculation using values from globals json + this.profileHelper.addSkillPointsToPlayer(pmcProfile, SkillTypes.INTELLECT, 0.05 * itemTpls.length); + } + /** * Get the tplid of an item from the examine request object * @param body response request @@ -910,4 +926,59 @@ export class InventoryController return output; } + + public redeemProfileReward(pmcData: IPmcData, request: IRedeemProfileRequestData, sessionId: string): IItemEventRouterResponse + { + const output = this.eventOutputHolder.getOutput(sessionId); + + const fullprofile = this.profileHelper.getFullProfile(sessionId); + for (const event of request.events) + { + // Hard coded to `SYSTEM` for now + // TODO: make this dynamic + const dialog = fullprofile.dialogues["59e7125688a45068a6249071"]; + const mail = dialog.messages.find(x => x._id === event.MessageId); + const mailEvent = mail.profileChangeEvents.find(x => x._id === event.EventId); + + switch (mailEvent.Type) + { + case "TraderSalesSum": + pmcData.TradersInfo[mailEvent.entity].salesSum = mailEvent.value; + this.logger.success(`Set trader ${mailEvent.entity}: Sales Sum to: ${mailEvent.value}`); + break; + case "TraderStanding": + pmcData.TradersInfo[mailEvent.entity].standing = mailEvent.value; + this.logger.success(`Set trader ${mailEvent.entity}: Standing to: ${mailEvent.value}`); + break; + case "ProfileLevel": + pmcData.Info.Experience = mailEvent.value; + pmcData.Info.Level = this.playerService.calculateLevel(pmcData); + this.logger.success(`Set profile xp to: ${mailEvent.value}`); + break; + case "SkillPoints": + { + const profileSkill = pmcData.Skills.Common.find(x => x.Id === mailEvent.entity); + profileSkill.Progress = mailEvent.value; + this.logger.success(`Set profile skill: ${mailEvent.entity} to: ${mailEvent.value}`); + break; + } + case "ExamineAllItems": + { + const itemsToInspect = this.itemHelper.getItems().filter(x => x._type !== "Node"); + this.flagItemsAsInspectedAndRewardXp(itemsToInspect.map(x => x._id), pmcData); + this.logger.success(`Flagged ${itemsToInspect.length} items as examined`); + break; + } + case "UnlockTrader": + pmcData.TradersInfo[mailEvent.entity].unlocked = true; + this.logger.success(`Trader ${mailEvent.entity} Unlocked`); + break; + default: + this.logger.success(`Unhandled profile reward event: ${mailEvent.Type}`); + break; + } + } + + return output; + } } diff --git a/project/src/models/eft/inventory/IRedeemProfileRequestData.ts b/project/src/models/eft/inventory/IRedeemProfileRequestData.ts new file mode 100644 index 00000000..7494089f --- /dev/null +++ b/project/src/models/eft/inventory/IRedeemProfileRequestData.ts @@ -0,0 +1,13 @@ +import { IInventoryBaseActionRequestData } from "./IInventoryBaseActionRequestData"; + +export interface IRedeemProfileRequestData extends IInventoryBaseActionRequestData +{ + Action: "RedeemProfileReward"; + events: IRedeemProfileRequestEvent[] +} + +export interface IRedeemProfileRequestEvent +{ + MessageId: string + EventId: string +} \ No newline at end of file diff --git a/project/src/models/eft/profile/IAkiProfile.ts b/project/src/models/eft/profile/IAkiProfile.ts index a2b7e5be..ed19575a 100644 --- a/project/src/models/eft/profile/IAkiProfile.ts +++ b/project/src/models/eft/profile/IAkiProfile.ts @@ -3,6 +3,7 @@ import { Item } from "@spt-aki/models/eft/common/tables/IItem"; import { EquipmentBuildType } from "@spt-aki/models/enums/EquipmentBuildType"; import { MemberCategory } from "@spt-aki/models/enums/MemberCategory"; import { MessageType } from "@spt-aki/models/enums/MessageType"; +import { IProfileChangeEvent } from "@spt-aki/models/spt/dialog/ISendMessageDetails"; export interface IAkiProfile { @@ -120,7 +121,7 @@ export interface Message items?: MessageItems; maxStorageTime?: number; systemData?: ISystemData; - profileChangeEvents?: any[]; + profileChangeEvents?: IProfileChangeEvent[]; } export interface MessagePreview diff --git a/project/src/models/enums/ItemEventActions.ts b/project/src/models/enums/ItemEventActions.ts index 6d805c03..0db4c02c 100644 --- a/project/src/models/enums/ItemEventActions.ts +++ b/project/src/models/enums/ItemEventActions.ts @@ -1,5 +1,4 @@ -export enum ItemEventActions -{ +export enum ItemEventActions { MOVE = "Move", REMOVE = "Remove", SPLIT = "Split", @@ -24,4 +23,5 @@ export enum ItemEventActions REMOVE_BUILD = "RemoveBuild", SAVE_EQUIPMENT_BUILD = "SaveEquipmentBuild", REMOVE_EQUIPMENT_BUILD = "RemoveEquipmentBuild", + REDEEM_PROFILE_REWARD = "RedeemProfileReward" } diff --git a/project/src/models/spt/config/IGiftsConfig.ts b/project/src/models/spt/config/IGiftsConfig.ts index f772f91b..c9ee799f 100644 --- a/project/src/models/spt/config/IGiftsConfig.ts +++ b/project/src/models/spt/config/IGiftsConfig.ts @@ -4,6 +4,7 @@ import { GiftSenderType } from "@spt-aki/models/enums/GiftSenderType"; import { SeasonalEventType } from "@spt-aki/models/enums/SeasonalEventType"; import { Traders } from "@spt-aki/models/enums/Traders"; import { IBaseConfig } from "@spt-aki/models/spt/config/IBaseConfig"; +import { IProfileChangeEvent } from "../dialog/ISendMessageDetails"; export interface IGiftsConfig extends IBaseConfig { @@ -29,4 +30,6 @@ export interface Gift timestampToSend?: number; associatedEvent: SeasonalEventType; collectionTimeHours: number; + /** Optional, can be used to change profile settings like level/skills */ + profileChangeEvents?: IProfileChangeEvent[]; } diff --git a/project/src/models/spt/dialog/ISendMessageDetails.ts b/project/src/models/spt/dialog/ISendMessageDetails.ts index 1365dfbc..8a803d37 100644 --- a/project/src/models/spt/dialog/ISendMessageDetails.ts +++ b/project/src/models/spt/dialog/ISendMessageDetails.ts @@ -27,6 +27,14 @@ export interface ISendMessageDetails systemData?: ISystemData; /** Optional - Used by ragfair messages */ ragfairDetails?: MessageContentRagfair; - /** Optional - Usage not known, unsure of purpose, even dumps dont have it */ - profileChangeEvents?: any[]; + /** OPTIONAL - allows modification of profile settings via mail */ + profileChangeEvents?: IProfileChangeEvent[]; +} + +export interface IProfileChangeEvent +{ + _id: string + Type: "TraderSalesSum" | "TraderStanding" | "ProfileLevel" | "SkillPoints" | "ExamineAllItems" | "UnlockTrader" + value: number + entity?: string } diff --git a/project/src/routers/item_events/InventoryItemEventRouter.ts b/project/src/routers/item_events/InventoryItemEventRouter.ts index 6793be7e..18bbe347 100644 --- a/project/src/routers/item_events/InventoryItemEventRouter.ts +++ b/project/src/routers/item_events/InventoryItemEventRouter.ts @@ -40,6 +40,7 @@ export class InventoryItemEventRouter extends ItemEventRouterDefinition new HandledRoute(ItemEventActions.EDIT_MAP_MARKER, false), new HandledRoute(ItemEventActions.OPEN_RANDOM_LOOT_CONTAINER, false), new HandledRoute(ItemEventActions.HIDEOUT_QTE_EVENT, false), + new HandledRoute(ItemEventActions.REDEEM_PROFILE_REWARD, false), ]; } @@ -90,6 +91,8 @@ export class InventoryItemEventRouter extends ItemEventRouterDefinition return this.inventoryCallbacks.openRandomLootContainer(pmcData, body, sessionID); case ItemEventActions.HIDEOUT_QTE_EVENT: return this.hideoutCallbacks.handleQTEEvent(pmcData, body, sessionID); + case ItemEventActions.REDEEM_PROFILE_REWARD: + return this.inventoryCallbacks.redeemProfileReward(pmcData, body, sessionID); default: throw new Error(`Unhandled event ${url}`); } diff --git a/project/src/services/GiftService.ts b/project/src/services/GiftService.ts index 4193f2a5..6a593fa5 100644 --- a/project/src/services/GiftService.ts +++ b/project/src/services/GiftService.ts @@ -77,6 +77,7 @@ export class GiftService playerId, giftData.localeTextId, giftData.items, + giftData.profileChangeEvents, this.timeUtil.getHoursAsSeconds(giftData.collectionTimeHours), ); } diff --git a/project/src/services/MailSendService.ts b/project/src/services/MailSendService.ts index 242dd658..6714c0c4 100644 --- a/project/src/services/MailSendService.ts +++ b/project/src/services/MailSendService.ts @@ -198,6 +198,7 @@ export class MailSendService sessionId: string, messageLocaleId: string, items: Item[] = [], + profileChangeEvents = [], maxStorageTimeSeconds = null, ): void { @@ -214,6 +215,11 @@ export class MailSendService details.itemsMaxStorageLifetimeSeconds = maxStorageTimeSeconds ?? 172800; // 48 hours if no value supplied } + if (profileChangeEvents.length > 0) + { + details.profileChangeEvents = profileChangeEvents; + } + this.sendMessageToPlayer(details); } @@ -279,6 +285,11 @@ export class MailSendService // Store reward items inside message and set appropriate flags inside message this.addRewardItemsToMessage(message, itemsToSendToPlayer, messageDetails.itemsMaxStorageLifetimeSeconds); + if (messageDetails.profileChangeEvents) + { + message.profileChangeEvents = messageDetails.profileChangeEvents; + } + // Add message to dialog senderDialog.messages.push(message);