diff --git a/project/src/callbacks/InventoryCallbacks.ts b/project/src/callbacks/InventoryCallbacks.ts index 26740265..a8e77593 100644 --- a/project/src/callbacks/InventoryCallbacks.ts +++ b/project/src/callbacks/InventoryCallbacks.ts @@ -34,7 +34,7 @@ export class InventoryCallbacks ) {} - /** Handle Move event */ + /** Handle client/game/profile/items/moving Move event */ public moveItem(pmcData: IPmcData, body: IInventoryMoveRequestData, sessionID: string): IItemEventRouterResponse { return this.inventoryController.moveItem(pmcData, body, sessionID); diff --git a/project/src/controllers/InventoryController.ts b/project/src/controllers/InventoryController.ts index 2109cca7..4c658857 100644 --- a/project/src/controllers/InventoryController.ts +++ b/project/src/controllers/InventoryController.ts @@ -1,6 +1,7 @@ import { inject, injectable } from "tsyringe"; import { LootGenerator } from "@spt-aki/generators/LootGenerator"; +import { HideoutHelper } from "@spt-aki/helpers/HideoutHelper"; import { InventoryHelper } from "@spt-aki/helpers/InventoryHelper"; import { ItemHelper } from "@spt-aki/helpers/ItemHelper"; import { PaymentHelper } from "@spt-aki/helpers/PaymentHelper"; @@ -59,6 +60,7 @@ export class InventoryController @inject("PresetHelper") protected presetHelper: PresetHelper, @inject("InventoryHelper") protected inventoryHelper: InventoryHelper, @inject("QuestHelper") protected questHelper: QuestHelper, + @inject("HideoutHelper") protected hideoutHelper: HideoutHelper, @inject("RagfairOfferService") protected ragfairOfferService: RagfairOfferService, @inject("ProfileHelper") protected profileHelper: ProfileHelper, @inject("PaymentHelper") protected paymentHelper: PaymentHelper, @@ -105,6 +107,7 @@ export class InventoryController // Check for item in inventory before allowing internal transfer const originalItemLocation = ownerInventoryItems.from.find((x) => x._id === moveRequest.item); + const originalLocationSlotId = originalItemLocation.slotId; if (!originalItemLocation) { // Internal item move but item never existed, possible dupe glitch @@ -116,6 +119,13 @@ export class InventoryController { return this.httpResponseUtil.appendErrorToOutput(output, moveResult.errorMessage); } + + // Item is moving into or out of place of fame dogtag slot + if (moveRequest.to.container.startsWith("dogtag") + || originalLocationSlotId.startsWith("dogtag")) + { + this.hideoutHelper.applyPlaceOfFameDogtagBonus(pmcData); + } } else { diff --git a/project/src/helpers/HideoutHelper.ts b/project/src/helpers/HideoutHelper.ts index 43550a6d..f08f74b3 100644 --- a/project/src/helpers/HideoutHelper.ts +++ b/project/src/helpers/HideoutHelper.ts @@ -4,7 +4,7 @@ import { InventoryHelper } from "@spt-aki/helpers/InventoryHelper"; import { ProfileHelper } from "@spt-aki/helpers/ProfileHelper"; import { IPmcData } from "@spt-aki/models/eft/common/IPmcData"; import { HideoutArea, IHideoutImprovement, Production, Productive } from "@spt-aki/models/eft/common/tables/IBotBase"; -import { Upd } from "@spt-aki/models/eft/common/tables/IItem"; +import { Item, Upd } from "@spt-aki/models/eft/common/tables/IItem"; import { StageBonus } from "@spt-aki/models/eft/hideout/IHideoutArea"; import { IHideoutContinuousProductionStartRequestData } from "@spt-aki/models/eft/hideout/IHideoutContinuousProductionStartRequestData"; import { IHideoutProduction } from "@spt-aki/models/eft/hideout/IHideoutProduction"; @@ -1043,4 +1043,60 @@ export class HideoutHelper } } } + + /** + * Add/remove bonus combat skill based on number of dogtags in place of fame hideout area + * @param pmcData Player profile + */ + public applyPlaceOfFameDogtagBonus(pmcData: IPmcData): void + { + const fameAreaProfile = pmcData.Hideout.Areas.find(area => area.type === HideoutAreas.PLACE_OF_FAME); + + // Get hideout area 16 bonus array + const fameAreaDb = this.databaseServer.getTables().hideout.areas.find((area) => area.type === HideoutAreas.PLACE_OF_FAME); + + // Get SkillGroupLevelingBoost object + const combatBoostBonusDb = fameAreaDb.stages[fameAreaProfile.level].bonuses.find(bonus => bonus.type === "SkillGroupLevelingBoost"); + + // Get SkillGroupLevelingBoost object in profile + const combatBonusProfile = pmcData.Bonuses.find(bonus => bonus.id === combatBoostBonusDb.id); + + // Get all slotted dogtag items + const activeDogtags = pmcData.Inventory.items.filter(item => item?.slotId?.startsWith("dogtag")); + + // Calculate bonus percent (apply hideoutManagement bonus) + const hideoutManagementSkill = this.profileHelper.getSkillFromProfile(pmcData, SkillTypes.HIDEOUT_MANAGEMENT); + const hideoutManagementSkillBonusPercent = 1 + (hideoutManagementSkill.Progress / 10000); // 5100 becomes 0.51, add 1 to it, 1.51 + const bonus = this.getDogtagCombatSkillBonusPercent(pmcData, activeDogtags) * hideoutManagementSkillBonusPercent; + + // Update bonus value to above calcualted value + combatBonusProfile.value = Number.parseFloat(bonus.toFixed(2)); + } + + /** + * Calculate the raw dogtag combat skill bonus for place of fame based on number of dogtags + * Reverse engineered from client code + * @param pmcData Player profile + * @param activeDogtags Active dogtags in place of fame dogtag slots + * @returns combat bonus + */ + protected getDogtagCombatSkillBonusPercent(pmcData: IPmcData, activeDogtags: Item[]): number + { + // Not own dogtag + // Side = opposite of player + let result = 0; + for (const dogtag of activeDogtags) + { + if (dogtag.upd.Dogtag.Side === pmcData.Info.Side + || Number.parseInt(dogtag.upd.Dogtag.AccountId) === pmcData.aid + ) + { + continue; + } + + result += 0.01 * dogtag.upd.Dogtag.Level; + } + + return result; + } }